/* ==================================================================== 
 * The Kannel Software License, Version 1.0 
 * 
 * Copyright (c) 2001-2018 Kannel Group  
 * Copyright (c) 1998-2001 WapIT Ltd.   
 * All rights reserved. 
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer. 
 * 
 * 2. Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in 
 *    the documentation and/or other materials provided with the 
 *    distribution. 
 * 
 * 3. The end-user documentation included with the redistribution, 
 *    if any, must include the following acknowledgment: 
 *       "This product includes software developed by the 
 *        Kannel Group (http://www.kannel.org/)." 
 *    Alternately, this acknowledgment may appear in the software itself, 
 *    if and wherever such third-party acknowledgments normally appear. 
 * 
 * 4. The names "Kannel" and "Kannel Group" must not be used to 
 *    endorse or promote products derived from this software without 
 *    prior written permission. For written permission, please  
 *    contact org@kannel.org. 
 * 
 * 5. Products derived from this software may not be called "Kannel", 
 *    nor may "Kannel" appear in their name, without prior written 
 *    permission of the Kannel Group. 
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED.  IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS 
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,  
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT  
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR  
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE  
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 * ==================================================================== 
 * 
 * This software consists of voluntary contributions made by many 
 * individuals on behalf of the Kannel Group.  For more information on  
 * the Kannel Group, please see <http://www.kannel.org/>. 
 * 
 * Portions of this software are based upon software originally written at  
 * WapIT Ltd., Helsinki, Finland for the Kannel project.  
 */ 

/*
 * wsp_headers.c - Implement WSP PDU headers
 * 
 * References:
 *   WSP specification version 1.1
 *   RFC 2068, Hypertext Transfer Protocol HTTP/1.1
 *   RFC 2616, Hypertext Transfer Protocol HTTP/1.1
 *
 *   For push headers, WSP specification, June 2000 conformance release
 *
 * This file has two parts.  The first part decodes the request's headers
 * from WSP to HTTP.  The second part encodes the response's headers from
 * HTTP to WSP.
 *
 * Note that push header encoding and decoding are divided two parts:
 * first decoding and encoding numeric values and then packing these values
 * into WSP format and unpacking them from WSP format. This module contains
 * only packing and unpacking parts.
 *
 * Some functions are declared non-static to provide them for external use,
 * ie. the MMS encapsulation encoding and decoding routines implemented in 
 * other files.
 *
 * Richard Braakman
 * Stipe Tolj <stolj@wapme.de>
 */

#include <string.h>
#include <limits.h>
#include <ctype.h>

#include "gwlib/gwlib.h"
#include "wsp.h"
#include "wsp_headers.h"
#include "wsp_strings.h"

/*
 * get field value and return its type as predefined data types
 * There are three kinds of field encodings:
 *   WSP_FIELD_VALUE_NUL_STRING: 0-terminated string
 *   WSP_FIELD_VALUE_ENCODED: short integer, range 0-127
 *   WSP_FIELD_VALUE_DATA: octet string defined by length
 * The function will return one of those values, and modify the parse context
 * to make it easy to get the field data.
 *   WSP_FIELD_VALUE_NUL_STRING: Leave parsing position at start of string
 *   WSP_FIELD_VALUE_ENCODED: Put value in *well_known_value, leave
 *        parsing position after field value.
 *   WSP_FIELD_VALUE_DATA: Leave parsing position at start of data, and set
 *        a parse limit at the end of data.
 */
int wsp_field_value(ParseContext *context, int *well_known_value)
{
    int val;
    unsigned long len;

    val = parse_get_char(context);
    if (val > 0 && val < 31) {
        *well_known_value = -1;
        parse_limit(context, val);
        return WSP_FIELD_VALUE_DATA;
    } else if (val == 31) {
        *well_known_value = -1;
        len = parse_get_uintvar(context);
        parse_limit(context, len);
        return WSP_FIELD_VALUE_DATA;
    } else if (val > 127) {
        *well_known_value = val - 128;
        return WSP_FIELD_VALUE_ENCODED;
    } else if (val == WSP_QUOTE || val == '"') {  /* 127 */
        *well_known_value = -1;
        /* We already consumed the Quote */
        return WSP_FIELD_VALUE_NUL_STRING;
    } else {    /* implicite val == 0 */ 
        *well_known_value = -1;
        /* Un-parse the character we just read */
        parse_skip(context, -1);
        return WSP_FIELD_VALUE_NUL_STRING;
    }
}

/* Skip over a field_value as defined above. */
void wsp_skip_field_value(ParseContext *context)
{
    int val;
    int ret;

    ret = wsp_field_value(context, &val);
    if (ret == WSP_FIELD_VALUE_DATA) {
        parse_skip_to_limit(context);
        parse_pop_limit(context);
    }
}

/* Multi-octet-integer is defined in 8.4.2.1 */
static long unpack_multi_octet_integer(ParseContext *context, long len)
{
    long val = 0;

    if (len > (long) sizeof(val) || len < 0)
        return -1;

    while (len > 0) {
        val = val * 256 + parse_get_char(context);
        len--;
    }

    if (parse_error(context))
        return -1;

    return val;
}

/* This function is similar to field_value, but it is used at various
 * places in the grammar where we expect either an Integer-value
 * or some kind of NUL-terminated text.
 * 
 * Return values are just like field_value except that WSP_FIELD_VALUE_DATA
 * will not be returned.
 *
 * As a special case, we parse a 0-length Long-integer as an
 * WSP_FIELD_VALUE_NONE, so that we can distinguish between No-value
 * and an Integer-value of 0.  (A real integer 0 would be encoded as
 * a Short-integer; the definition of Long-integer seems to allow
 * 0-length integers, but the definition of Multi-octet-integer does
 * not, so this is an unclear area of the specification.)
 */
int wsp_secondary_field_value(ParseContext *context, long *result)
{
    int val;
    long length;

    val = parse_get_char(context);
    if (val == 0) {
        *result = 0;
        return WSP_FIELD_VALUE_NONE;
    } else if (val > 0 && val < 31) {
        *result = unpack_multi_octet_integer(context, val);
        return WSP_FIELD_VALUE_ENCODED;
    } else if (val == 31) {
        length = parse_get_uintvar(context);
        *result = unpack_multi_octet_integer(context, length);
        return WSP_FIELD_VALUE_ENCODED;
    } else if (val > 127) {
        *result = val - 128;
        return WSP_FIELD_VALUE_ENCODED;
    } else if (val == WSP_QUOTE) {  /* 127 */
        *result = -1;
        return WSP_FIELD_VALUE_NUL_STRING;
    } else {
        *result = -1;
        /* Un-parse the character we just read */
        parse_skip(context, -1);
        return WSP_FIELD_VALUE_NUL_STRING;
    }
}

/* Integer-value is defined in 8.4.2.3 */
Octstr *wsp_unpack_integer_value(ParseContext *context)
{
    Octstr *decoded;
    unsigned long value;
    int val;

    val = parse_get_char(context);
    if (val < 31) {
        value = unpack_multi_octet_integer(context, val);
    } else if (val > 127) {
        value = val - 128;
    } else {
        warning(0, "WSP headers: bad integer-value.");
        return NULL;
    }

    decoded = octstr_create("");
    octstr_append_decimal(decoded, value);
    return decoded;
}

/* Q-value is defined in 8.4.2.3 */
static Octstr *convert_q_value(int q)
{
    Octstr *result = NULL;

    /* When quality factor 0 and quality factors with one or two
     * decimal digits are encoded, they shall be multiplied by 100
     * and incremented by one, so that they encode as a one-octet
     * value in range 1-100. */
    if (q >= 1 && q <= 100) {
        q = q - 1;
        result = octstr_create("0.");
        octstr_append_char(result, (q / 10) + '0');
        if (q % 10 > 0)
            octstr_append_char(result, (q % 10) + '0');
        return result;
    }

    /* Three decimal quality factors shall be multiplied with 1000
     * and incremented by 100. */
    if (q > 100 && q <= 1000) {
        q = q - 100;
        result = octstr_create("0.");
        octstr_append_char(result, (q / 100) + '0');
        if (q % 100 > 0)
            octstr_append_char(result, (q / 10 % 10) + '0');
        if (q % 10 > 0)
            octstr_append_char(result, (q % 10) + '0');
        return result;
    }

    return NULL;
}

/* Q-value is defined in 8.4.2.3 */
static Octstr *unpack_q_value(ParseContext *context)
{
    int c, c2;

    c = parse_get_char(context);
    if (c < 0)
        return NULL;

    if (c & 0x80) {
        c2 = parse_get_char(context);
        if (c2 < 0 || (c2 & 0x80))
            return NULL;
        c = ((c & 0x7f) << 8) + c2;
    }

    return convert_q_value(c);
}


/* Version-value is defined in 8.4.2.3. Encoding-Version uses coding
 * defined in this chapter, see 8.4.2.70.  */
Octstr *wsp_unpack_version_value(long value)
{
    Octstr *result;
    int major, minor;

    major = ((value >> 4) & 0x7);
    minor = (value & 0xf);

    result = octstr_create("");
    octstr_append_char(result, major + '0');
    if (minor != 15) {
        octstr_append_char(result, '.');
        octstr_append_decimal(result, minor);
    }

    return result;
}

static Octstr *unpack_encoding_version(ParseContext *context)
{
    int ch;

    ch = parse_get_char(context);
    if (ch < 128) {
        warning(0, "WSP: bad Encoding-Version value");
        return NULL;
    }

    return wsp_unpack_version_value(((long) ch) - 128);
}


/* Called with the parse limit set to the end of the parameter data,
 * and decoded containing the unpacked header line so far.
 * Parameter is defined in 8.4.2.4. */
static int unpack_parameter(ParseContext *context, Octstr *decoded)
{
    Octstr *parm = NULL;
    Octstr *value = NULL;
    int ret;
    long type;
    long val;

    ret = wsp_secondary_field_value(context, &type);
    if (parse_error(context) || ret == WSP_FIELD_VALUE_NONE) {
        warning(0, "bad parameter");
        goto error;
    }

    if (ret == WSP_FIELD_VALUE_ENCODED) {
        /* Typed-parameter */
        parm = wsp_parameter_to_string(type);
        if (!parm)
            warning(0, "Unknown parameter %02lx.", type);
    } else if (ret == WSP_FIELD_VALUE_NUL_STRING) {
        /* Untyped-parameter */
        parm = parse_get_nul_string(context);
        if (!parm)
            warning(0, "Format error in parameter.");
        type = -1;
        /* We treat Untyped-value as a special type.  Its format
         * Integer-value | Text-value is pretty similar to most
         * typed formats. */
    } else {
        panic(0, "Unknown secondary field value type %d.", ret);
    }

    if (type == 0x00) /* q */
        value = unpack_q_value(context);
    else {
        ret = wsp_secondary_field_value(context, &val);
        if (parse_error(context)) {
            warning(0, "bad parameter value");
            goto error;
        }

        if (ret == WSP_FIELD_VALUE_ENCODED) {
            switch (type) {
            case -1:  /* untyped: Integer-value */
            case 3:  /* type: Integer-value */
            case 8:  /* padding: Short-integer */
                value = octstr_create("");
                octstr_append_decimal(value, val);
                break;
            case 0:  /* q, already handled above */
                gw_assert(0);
                break;
            case 1:  /* charset: Well-known-charset */
                value = wsp_charset_to_string(val);
                if (!value)
                    warning(0, "Unknown charset %04lx.", val);
                break;
            case 2:  /* level: Version-value */
                value = wsp_unpack_version_value(val);
                break;
            case 5:  /* name: Text-string */
            case 6:  /* filename: Text-string */
                warning(0, "Text-string parameter with integer encoding");
                break;
            case 7:  /* differences: Field-name */
                value = wsp_header_to_string(val);
                if (!value)
                    warning(0, "Unknown differences header %02lx.", val);
                break;
            default:
                warning(0, "Unknown parameter encoding %02lx.",
                        type);
                break;
            }
        } else if (ret == WSP_FIELD_VALUE_NONE) {
            value = octstr_create("");
        } else {
            gw_assert(ret == WSP_FIELD_VALUE_NUL_STRING);
            /* Text-value = No-value | Token-text | Quoted-string */
            value = parse_get_nul_string(context);
            if (!value)
                warning(0, "Format error in parameter value.");
            else {
                if (octstr_get_char(value, 0) == '"') {
                    /* Quoted-string */
                    octstr_append_char(value, '"');
                } else { /* DAVI! */
                    octstr_insert(value, octstr_imm("\""), 0);
                    octstr_append_char(value, '"');
                }
            }
        }
    }

    if (!parm || !value) {
        warning(0, "Skipping parameters");
        goto error;
    }

    octstr_append(decoded, octstr_imm("; "));
    octstr_append(decoded, parm);
    if (octstr_len(value) > 0) {
        octstr_append_char(decoded, '=');
        octstr_append(decoded, value);
    }
    octstr_destroy(parm);
    octstr_destroy(value);
    return 0;

error:
    parse_skip_to_limit(context);
    octstr_destroy(parm);
    octstr_destroy(value);
    parse_set_error(context);
    return -1;
}

void wsp_unpack_all_parameters(ParseContext *context, Octstr *decoded)
{
    int ret = 0;

    while (ret >= 0 && !parse_error(context) &&
           parse_octets_left(context) > 0) {
        ret = unpack_parameter(context, decoded);
    }
}

/* Unpack parameters in the format used by credentials and challenge,
 * which differs from the format used by all other HTTP headers. */
static void unpack_broken_parameters(ParseContext *context, Octstr *decoded)
{
    int ret = 0;
    int first = 1;
    long pos;

    while (ret >= 0 && !parse_error(context) &&
	   parse_octets_left(context) > 0) {
        pos = octstr_len(decoded);
        ret = unpack_parameter(context, decoded);
        if (ret >= 0) {
            if (first) {
                /* Zap ';' */
                octstr_delete(decoded, pos, 1);
                first = 0;
            } else {
                /* Replace ';' with ',' */
                octstr_set_char(decoded, pos, first ? ' ' : ',');
            }
        }
    }
}

static void unpack_optional_q_value(ParseContext *context, Octstr *decoded)
{
    if (parse_octets_left(context) > 0) {
        Octstr *qval = unpack_q_value(context);
        if (qval) {
            octstr_append(decoded, octstr_imm("; q="));
            octstr_append(decoded, qval);
            octstr_destroy(qval);
        } else
            warning(0, "Bad q-value");
    }
}

/* Date-value is defined in 8.4.2.3. */
Octstr *wsp_unpack_date_value(ParseContext *context)
{
    unsigned long timeval;
    int length;

    length = parse_get_char(context);
    if (length > 30) {
        warning(0, "WSP headers: bad date-value.");
        return NULL;
    }

    timeval = unpack_multi_octet_integer(context, length);
    if (timeval < 0) {
        warning(0, "WSP headers: cannot unpack date-value.");
        return NULL;
    }

    return date_format_http(timeval);
}

/* Accept-general-form is defined in 8.4.2.7 */
Octstr *wsp_unpack_accept_general_form(ParseContext *context)
{
    Octstr *decoded = NULL;
    int ret;
    long val;

    /* The definition for Accept-general-form looks quite complicated,
     * but the "Q-token Q-value" part fits the normal expansion of
     * Parameter, so it simplifies to:
     *  Value-length Media-range *(Parameter)
     * and we've already parsed Value-length.
     */

    /* We use this function to parse content-general-form too,
     * because its definition of Media-type is identical to Media-range.
     */

    ret = wsp_secondary_field_value(context, &val);
    if (parse_error(context) || ret == WSP_FIELD_VALUE_NONE) {
        warning(0, "bad media-range or media-type");
        return NULL;
    }

    if (ret == WSP_FIELD_VALUE_ENCODED) {
        decoded = wsp_content_type_to_string(val);
        if (!decoded) {
            warning(0, "Unknown content type 0x%02lx.", val);
            return NULL;
        }
    } else if (ret == WSP_FIELD_VALUE_NUL_STRING) {
        decoded = parse_get_nul_string(context);
        if (!decoded) {
            warning(0, "Format error in content type");
            return NULL;
        }
    } else {
        panic(0, "Unknown secondary field value type %d.", ret);
    }

    wsp_unpack_all_parameters(context, decoded);
    return decoded;
}

/* Accept-charset-general-form is defined in 8.4.2.8 */
Octstr *wsp_unpack_accept_charset_general_form(ParseContext *context)
{
    Octstr *decoded = NULL;
    int ret;
    long val;

    ret = wsp_secondary_field_value(context, &val);
    if (parse_error(context) || ret == WSP_FIELD_VALUE_NONE) {
        warning(0, "Bad accept-charset-general-form");
        return NULL;
    }

    if (ret == WSP_FIELD_VALUE_ENCODED) {
        decoded = wsp_charset_to_string(val);
        if (!decoded) {
            warning(0, "Unknown character set %04lx.", val);
            return NULL;
        }
    } else if (ret == WSP_FIELD_VALUE_NUL_STRING) {
        decoded = parse_get_nul_string(context);
        if (!decoded) {
            warning(0, "Format error in accept-charset");
            return NULL;
        }
    } else {
        panic(0, "Unknown secondary field value type %d.", ret);
    }

    unpack_optional_q_value(context, decoded);
    return decoded;
}

/* Accept-language-general-form is defined in 8.4.2.10 */
static Octstr *unpack_accept_language_general_form(ParseContext *context)
{
    Octstr *decoded = NULL;
    int ret;
    long val;

    ret = wsp_secondary_field_value(context, &val);
    if (parse_error(context) || ret == WSP_FIELD_VALUE_NONE) {
        warning(0, "Bad accept-language-general-form");
        return NULL;
    }

    if (ret == WSP_FIELD_VALUE_ENCODED) {
        /* Any-language is handled by a special entry in the
         * language table. */
        decoded = wsp_language_to_string(val);
        if (!decoded) {
            warning(0, "Unknown language %02lx.", val);
            return NULL;
        }
    } else if (ret == WSP_FIELD_VALUE_NUL_STRING) {
        decoded = parse_get_nul_string(context);
        if (!decoded) {
            warning(0, "Format error in accept-language");
            return NULL;
        }
    } else {
        panic(0, "Unknown secondary field value type %d.", ret);
    }

    unpack_optional_q_value(context, decoded);
    return decoded;
}

/* Credentials is defined in 8.4.2.5 */
static Octstr *unpack_credentials(ParseContext *context)
{
    Octstr *decoded = NULL;
    int val;

    val = parse_peek_char(context);

    if (val == BASIC_AUTHENTICATION) {
        Octstr *userid, *password;

        parse_skip(context, 1);

        userid = parse_get_nul_string(context);
        password = parse_get_nul_string(context);

        if (parse_error(context)) {
            octstr_destroy(userid);
            octstr_destroy(password);
        } else {
            /* Create the user-pass cookie */
            decoded = octstr_duplicate(userid);
            octstr_append_char(decoded, ':');
            octstr_append(decoded, password);

            /* XXX Deal with cookies that overflow the 76-per-line
             * limit of base64.  Either go through and zap all
             * CR LF sequences, or give the conversion function
             * a flag or something to leave them out. */
            octstr_binary_to_base64(decoded);

            /* Zap the CR LF at the end */
            octstr_delete(decoded, octstr_len(decoded) - 2, 2);

            octstr_insert_data(decoded, 0, "Basic ", 6);

            octstr_destroy(userid);
            octstr_destroy(password);
        }
    } else if (val >= 32 && val < 128) {
        /* Generic authentication scheme */
        decoded = parse_get_nul_string(context);
        if (decoded)
            unpack_broken_parameters(context, decoded);
    }

    if (!decoded)
        warning(0, "Cannot parse credentials.");

    return decoded;
}

/* Credentials is defined in 8.4.2.5 
 * but as Proxy-Authentication is to be used by kannel, 
 * a simplier to parse version is used here */
static Octstr *proxy_unpack_credentials(ParseContext *context)
{
    Octstr *decoded = NULL;
    int val;

    val = parse_peek_char(context);

    if (val == BASIC_AUTHENTICATION) {
        Octstr *userid, *password;

        parse_skip(context, 1);

        userid = parse_get_nul_string(context);
        password = parse_get_nul_string(context);

        if (parse_error(context)) {
            octstr_destroy(userid);
            octstr_destroy(password);
        } else {
            /* Create the user-pass cookie */
            decoded = octstr_duplicate(userid);
            octstr_append_char(decoded, ':');
            octstr_append(decoded, password);
            octstr_destroy(userid);
            octstr_destroy(password);
        }
    } else if (val >= 32 && val < 128) {
        /* Generic authentication scheme */
        decoded = parse_get_nul_string(context);
        if (decoded)
            unpack_broken_parameters(context, decoded);
    }

    if (!decoded)
        warning(0, "Cannot parse credentials.");

    return decoded;
}

/* Challenge is defined in 8.4.2.5 */
static Octstr *unpack_challenge(ParseContext *context)
{
    Octstr *decoded = NULL;
    Octstr *realm_value = NULL;
    int val;

    val = parse_peek_char(context);
    if (val == BASIC_AUTHENTICATION) {
        parse_skip(context, 1);
        realm_value = parse_get_nul_string(context);
        if (realm_value) {
            decoded = octstr_create("Basic realm=\"");
            octstr_append(decoded, realm_value);
            octstr_append_char(decoded, '"');
        }
    } else if (val >= 32 && val < 128) {
        /* Generic authentication scheme */
        decoded = parse_get_nul_string(context);
        realm_value = parse_get_nul_string(context);
        if (decoded && realm_value) {
            octstr_append(decoded,
                          octstr_imm(" realm=\""));
            octstr_append(decoded, realm_value);
            octstr_append_char(decoded, '"');
            if (parse_octets_left(context) > 0) {
                /* Prepare for following parameter list */
                octstr_append_char(decoded, ',');
            }
            unpack_broken_parameters(context, decoded);
        }
    }

    if (!decoded)
        warning(0, "Cannot parse challenge.");

    octstr_destroy(realm_value);
    return decoded;
}

/* Content-range is defined in 8.4.2.23 */
static Octstr *unpack_content_range(ParseContext *context)
{
    /* We'd have to figure out how to access the content range
     * length (i.e. user_data size) from here to parse this,
     * and I don't see why the _client_ would send this in any case. */
    warning(0, "Decoding of content-range not supported");
    return NULL;

    /*
    	Octstr *decoded = NULL;
    	unsigned long first_byte_pos, entity_length;
    	unsigned long last_byte_pos;
     
    	first_byte_pos = parse_get_uintvar(context);
    	entity_length = parse_get_uintvar(context);
     
    	if (parse_error(context)) {
    		warning(0, "Cannot parse content-range header");
    		return NULL;
    	}
     
    	decoded = octstr_create("bytes ");
    	octstr_append_decimal(decoded, first_byte_pos);
    	octstr_append_char(decoded, '-');
    	octstr_append_decimal(decoded, last_byte_pos);
    	octstr_append_char(decoded, '/');
    	octstr_append_decimal(decoded, entity_length);
     
    	return decoded;
    */
}

/* Field-name is defined in 8.4.2.6 */
static Octstr *unpack_field_name(ParseContext *context)
{
    Octstr *decoded = NULL;
    int ret;
    int val;

    ret = wsp_field_value(context, &val);
    if (parse_error(context) || ret == WSP_FIELD_VALUE_DATA) {
        warning(0, "Bad field-name encoding");
        return NULL;
    }

    if (ret == WSP_FIELD_VALUE_ENCODED) {
        decoded = wsp_header_to_string(val);
        if (!decoded) {
            warning(0, "Unknown field-name 0x%02x.", val);
            return NULL;
        }
    } else if (ret == WSP_FIELD_VALUE_NUL_STRING) {
        decoded = parse_get_nul_string(context);
        if (!decoded) {
            warning(0, "Bad field-name encoding");
            return NULL;
        }
    } else {
        panic(0, "Unknown field value type %d.", ret);
    }

    return decoded;
}

/* Cache-directive is defined in 8.4.2.15 */
static Octstr *unpack_cache_directive(ParseContext *context)
{
    Octstr *decoded = NULL;
    int ret;
    int val;

    ret = wsp_field_value(context, &val);
    if (parse_error(context) || ret == WSP_FIELD_VALUE_DATA) {
        warning(0, "Bad cache-directive");
        goto error;
    }

    if (ret == WSP_FIELD_VALUE_ENCODED) {
        decoded = wsp_cache_control_to_string(val);
        if (!decoded) {
            warning(0, "Bad cache-directive 0x%02x.", val);
            goto error;
        }
        octstr_append_char(decoded, '=');
        switch (val) {
        case WSP_CACHE_CONTROL_NO_CACHE:
        case WSP_CACHE_CONTROL_PRIVATE:
            if (parse_octets_left(context) == 0) {
                warning(0, "Too short cache-directive");
                goto error;
            }
            octstr_append_char(decoded, '"');
            do {
                Octstr *fieldname = unpack_field_name(context);
                if (!fieldname) {
                    warning(0, "Bad field name in cache directive");
                    goto error;
                }
                octstr_append(decoded, fieldname);
                octstr_destroy(fieldname);
                if (parse_octets_left(context) > 0) {
                    octstr_append_char(decoded, ',');
                    octstr_append_char(decoded, ' ');
                }
            } while (parse_octets_left(context) > 0 &&
                     !parse_error(context));
            octstr_append_char(decoded, '"');
            break;
        case WSP_CACHE_CONTROL_MAX_AGE:
        case WSP_CACHE_CONTROL_MAX_STALE:
        case WSP_CACHE_CONTROL_MIN_FRESH:
            {
                Octstr *seconds;
                seconds = wsp_unpack_integer_value(context);
                if (!seconds) {
                    warning(0, "Bad integer value in cache directive");
                    goto error;
                }
                octstr_append(decoded, seconds);
                octstr_destroy(seconds);
            }
            break;
        default:
            warning(0, "Unexpected value 0x%02x in cache directive.", val);
            break;
        }
    } else if (ret == WSP_FIELD_VALUE_NUL_STRING) {
        /* XXX: WSP grammar seems wrong here.  It works out
         * to Token-text followed by Parameter.  But the
         * grammar in RFC2616 works out to a key = value
         * pair, i.e. only a Parameter. */
        decoded = parse_get_nul_string(context);
        if (!decoded) {
            warning(0, "Format error in cache-control.");
            return NULL;
        }
        /* Yes, the grammar allows only one */
        unpack_parameter(context, decoded);
    } else {
        panic(0, "Unknown field value type %d.", ret);
    }

    return decoded;

error:
    octstr_destroy(decoded);
    return NULL;
}

/* Retry-after is defined in 8.4.2.44 */
static Octstr *unpack_retry_after(ParseContext *context)
{
    int selector;

    selector = parse_get_char(context);
    if (selector == ABSOLUTE_TIME) {
        return wsp_unpack_date_value(context);
    } else if (selector == RELATIVE_TIME) {
        return wsp_unpack_integer_value(context);
    } else {
        warning(0, "Cannot parse retry-after value.");
        return NULL;
    }
}

/* Disposition is defined in 8.4.2.53 */
static Octstr *unpack_disposition(ParseContext *context)
{
    Octstr *decoded = NULL;
    int selector;

    selector = parse_get_char(context) - 128;
    decoded = wsp_disposition_to_string(selector);
    if (!decoded) {
        warning(0, "Cannot parse content-disposition value.");
        return NULL;
    }
    wsp_unpack_all_parameters(context, decoded);
    return decoded;
}

/* Range-value is defined in 8.4.2.42 */
static Octstr *unpack_range_value(ParseContext *context)
{
    Octstr *decoded = NULL;
    int selector;
    unsigned long first_byte_pos, last_byte_pos, suffix_length;

    selector = parse_get_char(context);
    if (selector == BYTE_RANGE) {
        first_byte_pos = parse_get_uintvar(context);
        if (parse_error(context))
            goto error;

        decoded = octstr_create("bytes = ");
        octstr_append_decimal(decoded, first_byte_pos);
        octstr_append_char(decoded, '-');

        last_byte_pos = parse_get_uintvar(context);
        if (parse_error(context)) {
            /* last_byte_pos is optional */
            parse_clear_error(context);
        } else {
            octstr_append_decimal(decoded, last_byte_pos);
        }
    } else if (selector == SUFFIX_BYTE_RANGE) {
        suffix_length = parse_get_uintvar(context);
        if (parse_error(context))
            goto error;

        decoded = octstr_create("bytes = -");
        octstr_append_decimal(decoded, suffix_length);
    } else {
        goto error;
    }

    return decoded;

error:
    warning(0, "Bad format for range-value.");
    octstr_destroy(decoded);
    return NULL;
}

/* Warning-value is defined in 8.4.2.51 */
static Octstr *unpack_warning_value(ParseContext *context)
{
    Octstr *decoded = NULL;
    Octstr *warn_code = NULL;
    Octstr *warn_agent = NULL;
    Octstr *warn_text = NULL;
    unsigned char quote = '"';

    warn_code = wsp_unpack_integer_value(context);

    warn_agent = parse_get_nul_string(context);
    if (warn_agent && octstr_get_char(warn_agent, 0) == WSP_QUOTE)
        octstr_delete(warn_agent, 0, 1);

    warn_text = parse_get_nul_string(context);
    if (warn_text && octstr_get_char(warn_text, 0) == WSP_QUOTE)
        octstr_delete(warn_text, 0, 1);
    if (octstr_get_char(warn_text, 0) != quote)
        octstr_insert_data(warn_text, 0, (char *)&quote, 1);
    if (octstr_get_char(warn_text, octstr_len(warn_text) - 1) != quote)
        octstr_append_char(warn_text, quote);

    if (parse_error(context) || !warn_agent || !warn_text)
        goto error;

    decoded = octstr_create("");
    octstr_append(decoded, warn_code);
    octstr_append_char(decoded, ' ');
    octstr_append(decoded, warn_agent);
    octstr_append_char(decoded, ' ');
    octstr_append(decoded, warn_text);

    octstr_destroy(warn_agent);
    octstr_destroy(warn_code);
    octstr_destroy(warn_text);
    return decoded;

error:
    warning(0, "Bad format for warning-value.");
    octstr_destroy(warn_agent);
    octstr_destroy(warn_code);
    octstr_destroy(warn_text);
    octstr_destroy(decoded);
    return NULL;
}

void wsp_unpack_well_known_field(List *unpacked, int field_type,
                                 ParseContext *context)
{
    int val, ret;
    unsigned char *headername = NULL;
    unsigned char *ch = NULL;
    Octstr *decoded = NULL;

    ret = wsp_field_value(context, &val);
    if (parse_error(context)) {
        warning(0, "Faulty header, skipping remaining headers.");
        parse_skip_to_limit(context);
        return;
    }

    headername = wsp_header_to_cstr(field_type);
    /* headername can still be NULL.  This is checked after parsing
     * the field value.  We want to parse the value before exiting,
     * so that we are ready for the next header. */

    /* The following code must set "ch" or "decoded" to a non-NULL
     * value if the header is valid. */

    if (ret == WSP_FIELD_VALUE_NUL_STRING) {
        /* We allow any header to have a text value, even if that
         * is not defined in the grammar.  Be generous in what
         * you accept, etc. */
        /* This covers Text-string, Token-Text, and Uri-value rules */
        decoded = parse_get_nul_string(context);
    } else if (ret == WSP_FIELD_VALUE_ENCODED) {
        switch (field_type) {
        case WSP_HEADER_ACCEPT:
        case WSP_HEADER_CONTENT_TYPE:
            ch = wsp_content_type_to_cstr(val);
            if (!ch)
                warning(0, "Unknown content type 0x%02x.", val);
            break;

        case WSP_HEADER_ACCEPT_CHARSET:
            ch = wsp_charset_to_cstr(val);
            if (!ch)
                warning(0, "Unknown charset 0x%02x.", val);
            break;

        case WSP_HEADER_ACCEPT_ENCODING:
        case WSP_HEADER_CONTENT_ENCODING:
            ch = wsp_encoding_to_cstr(val);
            if (!ch)
                warning(0, "Unknown encoding 0x%02x.", val);
            break;

        case WSP_HEADER_ACCEPT_LANGUAGE:
        case WSP_HEADER_CONTENT_LANGUAGE:
            ch = wsp_language_to_cstr(val);
            if (!ch)
                warning(0, "Unknown language 0x%02x.", val);
            break;

        case WSP_HEADER_ACCEPT_RANGES:
            ch = wsp_ranges_to_cstr(val);
            if (!ch)
                warning(0, "Unknown ranges value 0x%02x.", val);
            break;

        case WSP_HEADER_AGE:
        case WSP_HEADER_CONTENT_LENGTH:
        case WSP_HEADER_MAX_FORWARDS:
            /* Short-integer version of Integer-value */
            decoded = octstr_create("");
            octstr_append_decimal(decoded, val);
            break;

        case WSP_HEADER_ALLOW:
        case WSP_HEADER_PUBLIC:
            ch = wsp_method_to_cstr(val);
            if (!ch) {
                /* FIXME Support extended methods */
                warning(0, "Unknown method 0x%02x.", val);
            }
            break;

        case WSP_HEADER_CACHE_CONTROL:
        case WSP_HEADER_CACHE_CONTROL_V13:
        case WSP_HEADER_CACHE_CONTROL_V14:
            ch = wsp_cache_control_to_cstr(val);
            if (!ch)
                warning(0, "Unknown cache-control value 0x%02x.", val);
            break;

        case WSP_HEADER_CONNECTION:
            ch = wsp_connection_to_cstr(val);
            if (!ch)
                warning(0, "Unknown connection value 0x%02x.", val);
            break;


        case WSP_HEADER_PRAGMA:
            if (val == 0)
                ch = (unsigned char *)"no-cache";
            else
                warning(0, "Unknown pragma value 0x%02x.", val);
            break;

        case WSP_HEADER_TRANSFER_ENCODING:
            ch = wsp_transfer_encoding_to_cstr(val);
            if (!ch)
                warning(0, "Unknown transfer encoding value 0x%02x.", val);
            break;

        case WSP_HEADER_VARY:
            ch = wsp_header_to_cstr(val);
            if (!ch)
                warning(0, "Unknown Vary field name 0x%02x.", val);
            break;

        case WSP_HEADER_WARNING:
            decoded = octstr_create("");
            octstr_append_decimal(decoded, val);
            break;

        case WSP_HEADER_BEARER_INDICATION:
             ch = wsp_bearer_indication_to_cstr(val);
             if (!ch)
                 warning(0, "Unknown Bearer-Indication field name 0x%02x.", val);
             break;

        case WSP_HEADER_ACCEPT_APPLICATION:
             ch = wsp_application_id_to_cstr(val);
             if (!ch)
                 warning(0, "Unknown Accept-Application field name 0x%02x.", val);
             break;

        default:
            if (headername) {
                warning(0, "Did not expect short-integer with "
                        "'%s' header, skipping.", headername);
            }
            break;
        }
    } else if (ret == WSP_FIELD_VALUE_DATA) {
        switch (field_type) {
        case WSP_HEADER_ACCEPT:
        case WSP_HEADER_CONTENT_TYPE:
            /* Content-general-form and Accept-general-form
             * are defined separately in WSP, but their
             * definitions are equivalent. */
            decoded = wsp_unpack_accept_general_form(context);
            break;

        case WSP_HEADER_ACCEPT_CHARSET:
            decoded = wsp_unpack_accept_charset_general_form(context);
            break;

        case WSP_HEADER_ACCEPT_LANGUAGE:
            decoded = unpack_accept_language_general_form(context);
            break;

        case WSP_HEADER_AGE:
        case WSP_HEADER_CONTENT_LENGTH:
        case WSP_HEADER_MAX_FORWARDS:
        case WSP_HEADER_BEARER_INDICATION:
        case WSP_HEADER_ACCEPT_APPLICATION:
            /* Long-integer version of Integer-value */
            {
                long l = unpack_multi_octet_integer(context,
                                                    parse_octets_left(context));
                decoded = octstr_create("");
                octstr_append_decimal(decoded, l);
            }
            break;

        case WSP_HEADER_AUTHORIZATION:
            decoded = unpack_credentials(context);
            break;

        case WSP_HEADER_PROXY_AUTHORIZATION:
            decoded = proxy_unpack_credentials(context);
            break;

        case WSP_HEADER_CACHE_CONTROL:
            decoded = unpack_cache_directive(context);
            break;

        case WSP_HEADER_CONTENT_MD5:
            decoded = parse_get_octets(context,
                                       parse_octets_left(context));
            octstr_binary_to_base64(decoded);
            /* Zap the CR LF sequence at the end */
            octstr_delete(decoded, octstr_len(decoded) - 2, 2);
            break;

        case WSP_HEADER_CONTENT_RANGE:
            decoded = unpack_content_range(context);
            break;

        case WSP_HEADER_DATE:
        case WSP_HEADER_EXPIRES:
        case WSP_HEADER_IF_MODIFIED_SINCE:
        case WSP_HEADER_IF_RANGE:
        case WSP_HEADER_IF_UNMODIFIED_SINCE:
        case WSP_HEADER_LAST_MODIFIED:
            /* Back up to get the length byte again */
            parse_skip(context, -1);
            decoded = wsp_unpack_date_value(context);
            break;

        case WSP_HEADER_PRAGMA:
            /* The value is a bare Parameter, without a preceding
             * header body.  unpack_parameter wasn't really
             * designed for this.  We work around it here. */
            decoded = octstr_create("");
            if (unpack_parameter(context, decoded) < 0) {
                octstr_destroy(decoded);
                decoded = NULL;
            } else {
                /* Remove the leading "; " */
                octstr_delete(decoded, 0, 2);
            }
            break;

        case WSP_HEADER_PROXY_AUTHENTICATE:
        case WSP_HEADER_WWW_AUTHENTICATE:
            decoded = unpack_challenge(context);
            break;

        case WSP_HEADER_RANGE:
            decoded = unpack_range_value(context);
            break;

        case WSP_HEADER_RETRY_AFTER:
            decoded = unpack_retry_after(context);
            break;

        case WSP_HEADER_WARNING:
            decoded = unpack_warning_value(context);
            break;

        case WSP_HEADER_CONTENT_DISPOSITION:
            decoded = unpack_disposition(context);
            break;

        case WSP_HEADER_ENCODING_VERSION:
            decoded = unpack_encoding_version(context);
            break;

        default:
            if (headername) {
                warning(0, "Did not expect value-length with "
                        "'%s' header, skipping.", headername);
            }
            break;
        }
        if (headername && parse_octets_left(context) > 0) {
            warning(0, "WSP: %s: skipping %ld trailing octets.",
                    headername, parse_octets_left(context));
        }
        parse_skip_to_limit(context);
        parse_pop_limit(context);
    } else {
        panic(0, "Unknown field-value type %d.", ret);
    }

    if (ch == NULL && decoded != NULL)
        ch = (unsigned char *)octstr_get_cstr(decoded);
    if (ch == NULL)
        goto value_error;

    if (!headername) {
        warning(0, "Unknown header number 0x%02x.", field_type);
        goto value_error;
    }

    http_header_add(unpacked, (char *)headername,(char *) ch);
    octstr_destroy(decoded);
    return;

value_error:
    warning(0, "Skipping faulty header.");
    octstr_destroy(decoded);
}

void wsp_unpack_app_header(List *unpacked, ParseContext *context)
{
    Octstr *header = NULL;
    Octstr *value = NULL;

    header = parse_get_nul_string(context);
    value = parse_get_nul_string(context);

    if (header && value) {
        http_header_add(unpacked, octstr_get_cstr(header),
                        octstr_get_cstr(value));
    }

    if (parse_error(context))
        warning(0, "Error parsing application-header.");

    octstr_destroy(header);
    octstr_destroy(value);
}

List *wsp_headers_unpack(Octstr *headers, int content_type_present)
{
    ParseContext *context;
    int byte;
    List *unpacked;
    int code_page;

    unpacked = http_create_empty_headers();
    context = parse_context_create(headers);

    if (octstr_len(headers) > 0) {
        debug("wsp", 0, "WSP: decoding headers:");
        octstr_dump(headers, 0);
    }

    if (content_type_present)
        wsp_unpack_well_known_field(unpacked,
                                    WSP_HEADER_CONTENT_TYPE, context);

    code_page = 1;   /* default */

    while (parse_octets_left(context) > 0 && !parse_error(context)) {
        byte = parse_get_char(context);

        if (byte == 127 || (byte >= 1 && byte <= 31)) {
            if (byte == 127)
                code_page = parse_get_char(context);
            else
                code_page = byte;
            if (code_page == 1)
                info(0, "Returning to code page 1 (default).");
            else {
                warning(0, "Shift to unknown code page %d.",
                        code_page);
                warning(0, "Will try to skip headers until "
                        "next known code page.");
            }
        } else if (byte >= 128) {  /* well-known-header */
            if (code_page == 1)
                wsp_unpack_well_known_field(unpacked, byte - 128, context);
            else {
                debug("wsp", 0, "Skipping field 0x%02x.", byte);
                wsp_skip_field_value(context);
            }
        } else if (byte > 31 && byte < 127) {
            /* Un-parse the character we just read */
            parse_skip(context, -1);
            wsp_unpack_app_header(unpacked, context);
        } else {
            warning(0, "Unsupported token or header (start 0x%x)", byte);
            break;
        }
    }

    if (gwlist_len(unpacked) > 0) {
        long i;

        debug("wsp", 0, "WSP: decoded headers:");
        for (i = 0; i < gwlist_len(unpacked); i++) {
            Octstr *header = gwlist_get(unpacked, i);
            debug("wsp", 0, "%s", octstr_get_cstr(header));
        }
        debug("wsp", 0, "WSP: End of decoded headers.");
    }

    parse_context_destroy(context);
    return unpacked;
}


/**********************************************************************/
/* Start of header packing code (HTTP to WSP)                         */
/**********************************************************************/

static int pack_accept(Octstr *packet, Octstr *value);
static int pack_accept_charset(Octstr *packet, Octstr *value);
static int pack_accept_encoding(Octstr *packet, Octstr *value);
static int pack_accept_language(Octstr *packet, Octstr *value);
static int pack_cache_control(Octstr *packet, Octstr *value);
static int pack_challenge(Octstr *packet, Octstr *value);
static int pack_connection(Octstr *packet, Octstr *value);
static int pack_content_disposition(Octstr *packet, Octstr *value);
static int pack_content_range(Octstr *packet, Octstr *value);
static int pack_credentials(Octstr *packet, Octstr *value);
static int pack_encoding(Octstr *packet, Octstr *value);
static int pack_expires(Octstr *packet, Octstr *value);
static int pack_field_name(Octstr *packet, Octstr *value);
static int pack_if_range(Octstr *packet, Octstr *value);
static int pack_language(Octstr *packet, Octstr *value);
static int pack_md5(Octstr *packet, Octstr *value);
static int pack_method(Octstr *packet, Octstr *value);
static int pack_pragma(Octstr *packet, Octstr *value);
static int pack_range(Octstr *packet, Octstr *value);
static int pack_range_unit(Octstr *packet, Octstr *value);
static int pack_transfer_encoding(Octstr *packet, Octstr *value);
static int pack_uri(Octstr *packet, Octstr *value);
static int pack_warning(Octstr *packet, Octstr *value);



/* these are used in MMS encapsulation code too */

struct headerinfo headerinfo[] =
    {
        { WSP_HEADER_ACCEPT, pack_accept, LIST },
        { WSP_HEADER_ACCEPT_CHARSET, pack_accept_charset, LIST },
        { WSP_HEADER_ACCEPT_ENCODING, pack_accept_encoding, LIST },
        { WSP_HEADER_ACCEPT_LANGUAGE, pack_accept_language, LIST },
        { WSP_HEADER_ACCEPT_RANGES, pack_range_unit, LIST },
        { WSP_HEADER_AGE, wsp_pack_integer_string, 0 },
        /* pack_method is slightly too general because Allow is only
         * supposed to encode well-known-methods. */
        { WSP_HEADER_ALLOW, pack_method, LIST },
        { WSP_HEADER_AUTHORIZATION, pack_credentials, BROKEN_LIST },
        { WSP_HEADER_CACHE_CONTROL, pack_cache_control, LIST },
        { WSP_HEADER_CACHE_CONTROL_V13, pack_cache_control, LIST },
        { WSP_HEADER_CACHE_CONTROL_V14, pack_cache_control, LIST },
        { WSP_HEADER_CONNECTION, pack_connection, LIST },
        { WSP_HEADER_CONTENT_BASE, pack_uri, 0 },
        { WSP_HEADER_CONTENT_ENCODING, pack_encoding, LIST },
        { WSP_HEADER_CONTENT_LANGUAGE, pack_language, LIST },
        { WSP_HEADER_CONTENT_LENGTH, wsp_pack_integer_string, 0 },
        { WSP_HEADER_CONTENT_LOCATION, pack_uri, 0 },
        { WSP_HEADER_CONTENT_MD5, pack_md5, 0 },
        { WSP_HEADER_CONTENT_RANGE, pack_content_range, 0 },
        { WSP_HEADER_CONTENT_TYPE, wsp_pack_content_type, 0 },
        { WSP_HEADER_DATE, wsp_pack_date, 0 },
        { WSP_HEADER_ETAG, wsp_pack_quoted_text, 0 },
        { WSP_HEADER_EXPIRES, pack_expires, 0 },
        { WSP_HEADER_FROM, wsp_pack_text, 0 },
        { WSP_HEADER_HOST, wsp_pack_text, 0 },
        { WSP_HEADER_IF_MODIFIED_SINCE, wsp_pack_date, 0 },
        { WSP_HEADER_IF_MATCH, wsp_pack_quoted_text, 0 },
        { WSP_HEADER_IF_NONE_MATCH, wsp_pack_quoted_text, 0 },
        { WSP_HEADER_IF_RANGE, pack_if_range, 0 },
        { WSP_HEADER_IF_UNMODIFIED_SINCE, wsp_pack_date, 0 },
        { WSP_HEADER_LAST_MODIFIED, wsp_pack_date, 0 },
        { WSP_HEADER_LOCATION, pack_uri, 0 },
        { WSP_HEADER_MAX_FORWARDS, wsp_pack_integer_string, 0 },
        { WSP_HEADER_PRAGMA, pack_pragma, LIST },
        { WSP_HEADER_PROXY_AUTHENTICATE, pack_challenge, BROKEN_LIST },
        { WSP_HEADER_PROXY_AUTHORIZATION, pack_credentials, BROKEN_LIST },
        { WSP_HEADER_PUBLIC, pack_method, LIST },
        { WSP_HEADER_RANGE, pack_range, 0 },
        { WSP_HEADER_REFERER, pack_uri, 0 },
        { WSP_HEADER_RETRY_AFTER, wsp_pack_retry_after, 0 },
        { WSP_HEADER_SERVER, wsp_pack_text, 0 },
        { WSP_HEADER_TRANSFER_ENCODING, pack_transfer_encoding, LIST },
        { WSP_HEADER_UPGRADE, wsp_pack_text, LIST },
        { WSP_HEADER_USER_AGENT, wsp_pack_text, 0 },
        { WSP_HEADER_VARY, pack_field_name, LIST },
        { WSP_HEADER_VIA, wsp_pack_text, LIST },
        { WSP_HEADER_WARNING, pack_warning, LIST },
        { WSP_HEADER_WWW_AUTHENTICATE, pack_challenge, BROKEN_LIST },
        { WSP_HEADER_CONTENT_DISPOSITION, pack_content_disposition, 0 },
        { WSP_HEADER_PUSH_FLAG, wsp_pack_integer_string, 0},
        { WSP_HEADER_X_WAP_CONTENT_URI, pack_uri, 0},
        { WSP_HEADER_X_WAP_INITIATOR_URI, pack_uri, 0},
        { WSP_HEADER_X_WAP_APPLICATION_ID, wsp_pack_integer_string, 0},
        { WSP_HEADER_CONTENT_ID, wsp_pack_quoted_text, 0},
        { WSP_HEADER_ENCODING_VERSION, wsp_pack_version_value, 0 }
        // DAVI { WSP_HEADER_SET_COOKIE, pack_version_value, 0 }
    };

static Parameter *parm_create(Octstr *key, Octstr *value)
{
    Parameter *parm;

    parm = gw_malloc(sizeof(*parm));
    parm->key = key;
    parm->value = value;
    return parm;
}

static void parm_destroy(Parameter *parm)
{
    if (parm == NULL)
        return;

    octstr_destroy(parm->key);
    octstr_destroy(parm->value);
    gw_free(parm);
}

void parm_destroy_item(void *parm)
{
    parm_destroy(parm);
}

static Parameter *parm_parse(Octstr *value)
{
    long pos;
    Octstr *key, *val;

    pos = octstr_search_char(value, '=', 0);
    if (pos > 0) {
        key = octstr_copy(value, 0, pos);
        val = octstr_copy(value, pos + 1, octstr_len(value) - pos);
        octstr_strip_blanks(key);
        octstr_strip_blanks(val);
    } else {
        key = octstr_duplicate(value);
        val = NULL;
    }

    return parm_create(key, val);
}

/* Many HTTP field elements can take parameters in a standardized
 * form: parameters appear after the main value, each is introduced
 * by a semicolon (;), and consists of a key=value pair or just
 * a key, where the key is a token and the value is either a token
 * or a quoted-string.
 * The main value itself is a series of tokens, separators, and
 * quoted-strings.
 *
 * This function will take such a field element, and remove all
 * parameters from it.  The parameters are returned as a List
 * of Parameter, where the value field is left NULL
 * if the parameter was just a key.
 * It returns NULL if there were no parameters.
 */
List *wsp_strip_parameters(Octstr *value)
{
    long pos;
    long len;
    int c;
    long end;
    List *parms;
    long firstparm;

    len = octstr_len(value);
    /* Find the start of the first parameter. */
    for (pos = 0; pos < len; pos++) {
        c = octstr_get_char(value, pos);
        if (c == ';')
            break;
        else if (c == '"')
            pos += http_header_quoted_string_len(value, pos) - 1;
    }

    if (pos >= len)
        return NULL;   /* no parameters */

    parms = gwlist_create();
    firstparm = pos;

    for (pos++; pos > 0 && pos < len; pos++) {
        Octstr *key = NULL;
        Octstr *val = NULL;

        end = octstr_search_char(value, '=', pos);
        if (end < 0)
            end = octstr_search_char(value, ';', pos);
        if (end < 0)
            end = octstr_len(value);
        key = octstr_copy(value, pos, end - pos);
        octstr_strip_blanks(key);
        pos = end;

        if (octstr_get_char(value, pos) == '=') {
            pos++;
            while (isspace(octstr_get_char(value, pos)))
                pos++;
            if (octstr_get_char(value, pos) == '"')
                end = pos + http_header_quoted_string_len(value, pos);
            else
                end = octstr_search_char(value, ';', pos);
            if (end < 0)
                end = octstr_len(value);
            val = octstr_copy(value, pos, end - pos);
            octstr_strip_blanks(val);
            pos = end;
            pos = octstr_search_char(value, ';', pos);
        }

        gwlist_append(parms, parm_create(key, val));
    }

    octstr_delete(value, firstparm, octstr_len(value) - firstparm);
    octstr_strip_blanks(value);
    return parms;
}

int wsp_pack_text(Octstr *packed, Octstr *text)
{
    /* This check catches 0-length strings as well, because
     * octstr_get_char will return -1. */
    if (octstr_get_char(text, 0) >= 128 || octstr_get_char(text, 0) < 32)
        octstr_append_char(packed, WSP_QUOTE);
    octstr_append(packed, text);
    octstr_append_char(packed, 0);
    return 0;
}

/* Pack a string as quoted-text WAP WSP 203, Section 8.4.2.1 */
int wsp_pack_quoted_text(Octstr *packed, Octstr *text)
{
     octstr_append_char(packed, '"');
     octstr_append(packed,text);
     octstr_append_char(packed,0);
     return 0;
}

/* Pack text as Quoted-string if it starts with a " character.
 * Pack it as Text-string otherwise. */
static void pack_quoted_string(Octstr *packed, Octstr *text)
{
    octstr_append(packed, text);
    if (octstr_get_char(text, octstr_len(text) - 1) == '"' &&
        octstr_get_char(text, 0) == '"')
        octstr_delete(packed, octstr_len(packed) - 1, 1);
    octstr_append_char(packed, 0);
}

/* Is this char in the 'separators' set defined by HTTP? */
static int is_separator_char(int c)
{
    switch (c) {
    case '(':
    case ')':
    case '<':
    case '>':
    case '@':
    case ',':
    case ';':
    case ':':
    case '\\':
    case '"':
    case '/':
    case '[':
    case ']':
    case '?':
    case '=':
    case '{':
    case '}':
    case 32:  /* SP */
    case 9:   /* HT */
        return 1;
    default:
        return 0;
    }
}

/* Is this char part of a 'token' as defined by HTTP? */
static int is_token_char(int c)
{
    return c >= 32 && c < 127 && !is_separator_char(c);
}

/* Is this string a 'token' as defined by HTTP? */
static int is_token(Octstr *token)
{
    return octstr_len(token) > 0 &&
           octstr_check_range(token, 0, octstr_len(token), is_token_char);
}

/* We represent qvalues as integers from 0 through 1000, rather than
 * as floating values. */
static int parse_qvalue(Octstr *value)
{
    int qvalue;

    if (value == NULL)
        return -1;

    if (!isdigit(octstr_get_char(value, 0)))
        return -1;

    qvalue = (octstr_get_char(value, 0) - '0') * 1000;

    if (octstr_get_char(value, 1) != '.')
        goto gotvalue;

    if (!isdigit(octstr_get_char(value, 2)))
        goto gotvalue;
    qvalue += (octstr_get_char(value, 2) - '0') * 100;


    if (!isdigit(octstr_get_char(value, 3)))
        goto gotvalue;
    qvalue += (octstr_get_char(value, 3) - '0') * 10;

    if (!isdigit(octstr_get_char(value, 4)))
        goto gotvalue;
    qvalue += (octstr_get_char(value, 4) - '0');

gotvalue:
    if (qvalue < 0 || qvalue > 1000)
        return -1;

    return qvalue;
}

static int get_qvalue(List *parms, int default_qvalue)
{
    long i;
    Parameter *parm;
    int qvalue;

    for (i = 0; i < gwlist_len(parms); i++) {
        parm = gwlist_get(parms, i);
        if (octstr_str_compare(parm->key, "q") == 0 ||
            octstr_str_compare(parm->key, "Q") == 0) {
            qvalue = parse_qvalue(parm->value);
            if (qvalue >= 0)
                return qvalue;
        }
    }

    return default_qvalue;
}

static int pack_qvalue(Octstr *packed, int qvalue)
{
    /* "Quality factor 1 is the default value and shall never
     * be sent." */
    if (qvalue == 1000)
        return -1;

    /* Remember that our qvalues are already multiplied by 1000. */
    if (qvalue % 10 == 0)
        qvalue = qvalue / 10 + 1;
    else
        qvalue = qvalue + 100;
    octstr_append_uintvar(packed, qvalue);
    return 0;
}

/* Pack value as a Value-length followed by the encoded value. */
void wsp_pack_value(Octstr *packed, Octstr *encoded)
{
    long len;

    len = octstr_len(encoded);
    if (len <= 30)
        octstr_append_char(packed, len);
    else {
        octstr_append_char(packed, 31);
        octstr_append_uintvar(packed, len);
    }

    octstr_append(packed, encoded);
}

void wsp_pack_long_integer(Octstr *packed, unsigned long integer)
{
    long oldlen = octstr_len(packed);
    unsigned char octet;
    long len;

    if (integer == 0) {
	/* The Multi-octet-integer has to be at least 1 octet long. */
	octstr_append_char(packed, 1); /* length */
	octstr_append_char(packed, 0); /* value */
	return;
    }

    /* Encode it back-to-front, by repeatedly inserting
     * at the same position, because that's easier. */
    for (len = 0; integer != 0; integer >>= 8, len++) {
        octet = integer & 0xff;
        octstr_insert_data(packed, oldlen, (char *)&octet, 1);
    }

    octet = len;
    octstr_insert_data(packed, oldlen, (char *)&octet, 1);
}

void wsp_pack_short_integer(Octstr *packed, unsigned long integer)
{
    gw_assert(integer <= MAX_SHORT_INTEGER);

    octstr_append_char(packed, integer + 0x80);
}

void wsp_pack_integer_value(Octstr *packed, unsigned long integer)
{
    if (integer <= MAX_SHORT_INTEGER)
        wsp_pack_short_integer(packed, integer);
    else
        wsp_pack_long_integer(packed, integer);
}

int wsp_pack_integer_string(Octstr *packed, Octstr *value)
{
    unsigned long integer;
    long pos;
    int c;
    int digit;

    integer = 0;
    for (pos = 0; pos < octstr_len(value); pos++) {
        c = octstr_get_char(value, pos);
        if (!isdigit(c))
            break;
        digit = c - '0';
        if (integer > ULONG_MAX / 10)
            goto overflow;
        integer *= 10;
        if (integer > ULONG_MAX - digit)
            goto overflow;
        integer += digit;
    }

    debug("wsp",0,"WSP: %s: value `%s', integer 0x%04lx", __func__,
    	  octstr_get_cstr(value), integer);
    wsp_pack_integer_value(packed, integer);
    return 0;

overflow:
    warning(0, "WSP: Number too large to handle: '%s'.",
            octstr_get_cstr(value));
    return -1;
}


int wsp_pack_version_value(Octstr *packed, Octstr *version)
{
	unsigned long integer;
    long major, minor;
    long pos;

    pos = octstr_parse_long(&major, version, 0, 10);
    if (pos < 0 || major < 1 || major > 7)
        goto usetext;

    if (pos == octstr_len(version))
        minor = 15;
    else {
        if (octstr_get_char(version, pos) != '.')
            goto usetext;
        pos = octstr_parse_long(&minor, version, pos + 1, 10);
        if (pos != octstr_len(version) || minor < 0 || minor > 14)
            goto usetext;
    }

    integer = major << 4 | minor;
    debug("wsp",0,"WSP: %s: value `%s', integer 0x%04lx", __func__,
    	  octstr_get_cstr(version), integer);
    wsp_pack_short_integer(packed, integer);
    return 0;

usetext:
    wsp_pack_text(packed, version);
    return 0;
}

int wsp_pack_constrained_value(Octstr *packed, Octstr *text, long value)
{
    if (value >= 0)
        wsp_pack_short_integer(packed, value);
    else
        wsp_pack_text(packed, text);
    return 0;
}

static void pack_parameter(Octstr *packed, Parameter *parm)
{
    long keytoken;
    long tmp;
    long start;

    start = octstr_len(packed);

    /* Parameter = Typed-parameter | Untyped-parameter */
    /* keytoken = wsp_string_to_parameter(parm->key); */
    /* XXX this should obey what kind of WSP Encoding-Version the client is using */
    keytoken = wsp_string_to_versioned_parameter(parm->key, WSP_1_2);
   
    if (keytoken >= 0) {
        /* Typed-parameter = Well-known-parameter-token Typed-value */
        /* Well-known-parameter-token = Integer-value */
        wsp_pack_integer_value(packed, keytoken);
        /* Typed-value = Compact-value | Text-value */
        /* First try to pack as Compact-value or No-value.
         * If that fails, pack as Text-value. */
        if (parm->value == NULL) {
            octstr_append_char(packed, 0);  /* No-value */
            return;
        } else switch (keytoken) {
            case 0:   /* q */
                tmp = parse_qvalue(parm->value);
                if (tmp >= 0) {
                    if (pack_qvalue(packed, tmp) < 0)
                        octstr_delete(packed, start,
                                      octstr_len(packed) - start);
                    return;
                }
                break;
            case 1:  /* charset */
                tmp = wsp_string_to_charset(parm->value);
                if (tmp >= 0) {
                    wsp_pack_integer_value(packed, tmp);
                    return;
                }
                break;
            case 2:  /* level */
                wsp_pack_version_value(packed, parm->value);
                return;
            case 3:  /* type */
                if (octstr_check_range(parm->value, 0,
                                       octstr_len(parm->value), 
				       gw_isdigit) &&
                    wsp_pack_integer_string(packed, parm->value) >= 0)
                    return;
                break;
            case 5:  /* name */
            case 6:  /* filename */
                break;
            case 7:  /* differences */
                if (pack_field_name(packed, parm->value) >= 0)
                    return;
                break;
            case 8:  /* padding */
                if (octstr_parse_long(&tmp, parm->value, 0, 10)
                    == octstr_len(parm->value) &&
                    tmp >= 0 && tmp <= MAX_SHORT_INTEGER) {
                    wsp_pack_short_integer(packed, tmp);
                    return;
                }
                break;
            }
        pack_quoted_string(packed, parm->value);
    } else {
        /* Untyped-parameter = Token-text Untyped-value */
        wsp_pack_text(packed, parm->key);
        /* Untyped-value = Integer-value | Text-value */
        if (parm->value == NULL) {
            octstr_append_char(packed, 0);  /* No-value */
            return;
        }
        /* If we can pack as integer, do so. */
        if (octstr_parse_long(&tmp, parm->value, 0, 10)
            == octstr_len(parm->value)) {
            wsp_pack_integer_value(packed, tmp);
        } else {
            pack_quoted_string(packed, parm->value);
        }
    }
}

void wsp_pack_parameters(Octstr *packed, List *parms)
{
    long i;
    Parameter *parm;

    for (i = 0; i < gwlist_len(parms); i++) {
        parm = gwlist_get(parms, i);
        pack_parameter(packed, parm);
    }
}

static int pack_uri(Octstr *packed, Octstr *value)
{
    wsp_pack_text(packed, value);
    return 0;
}

static int pack_md5(Octstr *packed, Octstr *value)
{
    Octstr *binary;

    binary = octstr_duplicate(value);
    octstr_base64_to_binary(binary);

    if (octstr_len(binary) != 16) {
        error(0, "WSP: MD5 value not 128 bits.");
        return -1;
    }

    octstr_append_char(packed, 16);
    octstr_append(packed, binary);

    octstr_destroy(binary);

    return 0;
}

/* Actually packs a "Value-length Challenge" */
/* Relies on http_split_auth_value to have converted the entry to
 * the normal HTTP parameter format rather than the comma-separated
 * one used by challenge and credentials. */
static int pack_challenge(Octstr *packed, Octstr *value)
{
    Octstr *encoding = NULL;
    Octstr *scheme = NULL;
    Octstr *basic = octstr_imm("Basic");
    Octstr *realm = octstr_imm("realm");
    Octstr *parmstring = NULL;
    List *parms = NULL;
    Parameter *realmparm = NULL;
    long realmpos = -1;
    Octstr *realmval = NULL;
    long pos;

    encoding = octstr_create("");

    /* Get authentication scheme */
    for (pos = 0; pos < octstr_len(value); pos++) {
        if (!is_token_char(octstr_get_char(value, pos)))
            break;
    }
    scheme = octstr_copy(value, 0, pos);
    octstr_strip_blanks(scheme);

    /* Skip whitespace */
    while (isspace(octstr_get_char(value, pos)))
        pos++;

    if (octstr_case_compare(scheme, basic) == 0) {
        parmstring = octstr_copy(value, pos, octstr_len(value) - pos);
        realmparm = parm_parse(parmstring);

        octstr_append_char(encoding, BASIC_AUTHENTICATION);
        realmpos = octstr_len(encoding);
    } else {
        long i;

        wsp_pack_text(encoding, scheme);
        realmpos = octstr_len(encoding);

        /* Find the realm parameter and exclude it */
        parms = wsp_strip_parameters(value);
        for (i = 0; i < gwlist_len(parms); i++) {
            Parameter *parm = gwlist_get(parms, i);
            if (octstr_case_compare(realm, parm->key) == 0) {
                realmparm = parm;
                gwlist_delete(parms, i, 1);
                break;
            }
        }

        wsp_pack_parameters(encoding, parms);
    }

    /*
     * In the WSP encoding we have to put the realm value first, but
     * with non-Basic challenges we don't know if it will come first
     * in the HTTP header.  So we just start parsing parameters, and
     * go back and insert the realm value later.  The same technique
     * is used for Basic authentication to simplify the code.
     */

    if (realmparm == NULL ||
        octstr_case_compare(realmparm->key, realm) != 0 ||
        realmparm->value == NULL)
        goto error;

    /* Zap quote marks */
    if (octstr_get_char(realmparm->value, 0) == '"' &&
        octstr_get_char(realmparm->value, octstr_len(realmparm->value) - 1) == '"') {
        octstr_delete(realmparm->value, 0, 1);
        octstr_delete(realmparm->value, octstr_len(realmparm->value) - 1, 1);
    }

    gw_assert(realmpos >= 0);

    realmval = octstr_create("");
    wsp_pack_text(realmval, realmparm->value);
    octstr_insert(encoding, realmval, realmpos);

    wsp_pack_value(packed, encoding);

    octstr_destroy(encoding);
    octstr_destroy(scheme);
    octstr_destroy(parmstring);
    parm_destroy(realmparm);
    gwlist_destroy(parms, parm_destroy_item);
    octstr_destroy(realmval);
    return 0;

error:
    warning(0, "WSP: Cannot parse challenge.");
    octstr_destroy(encoding);
    octstr_destroy(scheme);
    octstr_destroy(parmstring);
    parm_destroy(realmparm);
    gwlist_destroy(parms, parm_destroy_item);
    octstr_destroy(realmval);
    return -1;
}

/* Actually packs a "Value-length Credentials" */
/* Relies on http_split_auth_value to have converted the entry to
 * the normal HTTP parameter format rather than the comma-separated
 * one used by challenge and credentials. */
static int pack_credentials(Octstr *packed, Octstr *value)
{
    Octstr *encoding = NULL;
    Octstr *scheme = NULL;
    Octstr *basic = NULL;
    long pos;

    encoding = octstr_create("");

    /* Get authentication scheme */
    for (pos = 0; pos < octstr_len(value); pos++) {
        if (!is_token_char(octstr_get_char(value, pos)))
            break;
    }
    scheme = octstr_copy(value, 0, pos);
    octstr_strip_blanks(scheme);

    /* Skip whitespace */
    while (isspace(octstr_get_char(value, pos)))
        pos++;

    basic = octstr_imm("Basic");
    if (octstr_case_compare(scheme, basic) == 0) {
        Octstr *cookie;
        Octstr *userid;
        Octstr *password;

        octstr_append_char(encoding, BASIC_AUTHENTICATION);

        cookie = octstr_copy(value, pos, octstr_len(value) - pos);
        octstr_base64_to_binary(cookie);
        pos = octstr_search_char(cookie, ':', 0);
        if (pos < 0) {
            warning(0, "WSP: bad cookie in credentials '%s'.",
                    octstr_get_cstr(value));
            octstr_destroy(cookie);
            goto error;
        }

        userid = octstr_copy(cookie, 0, pos);
        password = octstr_copy(cookie, pos + 1, octstr_len(cookie) - pos);
        wsp_pack_text(encoding, userid);
        wsp_pack_text(encoding, password);

        octstr_destroy(cookie);
        octstr_destroy(userid);
        octstr_destroy(password);
    } else {
        List *parms;

        wsp_pack_text(encoding, scheme);
        parms = wsp_strip_parameters(value);
        wsp_pack_parameters(encoding, parms);
        gwlist_destroy(parms, parm_destroy_item);
    }

    wsp_pack_value(packed, encoding);
    octstr_destroy(encoding);
    octstr_destroy(scheme);

    return 0;

error:
    octstr_destroy(encoding);
    octstr_destroy(scheme);
    return -1;
}

int wsp_pack_date(Octstr *packed, Octstr *value)
{
    long timeval;

    /* If we get a negative timeval here, this means either
     * we're beyond the time_t 32 bit int positive border for the 
     * timestamp or we're really handling time before epoch. */
    timeval = date_parse_http(value);
    if (timeval == -1) {
        warning(0, "WSP headers: cannot decode date '%s'",
                octstr_get_cstr(value));
        return -1;
    }

    /* We'll assume that we don't package time before epoch. */
    wsp_pack_long_integer(packed, (unsigned long) timeval);
    return 0;
}

static int pack_connection(Octstr *packed, Octstr *value)
{
    return wsp_pack_constrained_value(packed, value,
                                      wsp_string_to_connection(value));
}

static int pack_encoding(Octstr *packed, Octstr *value)
{
    return wsp_pack_constrained_value(packed, value,
                                      wsp_string_to_encoding(value));
}

static int pack_field_name(Octstr *packed, Octstr *value)
{
    /* XXX we need to obey which WSP encoding-version to use */
    /* return pack_constrained_value(packed, value,
                                  wsp_string_to_header(value)); */
    return wsp_pack_constrained_value(packed, value,
                                      wsp_string_to_versioned_header(value, WSP_1_2));
}

static int pack_language(Octstr *packed, Octstr *value)
{
    long language;

    /* Can't use pack_constrained_value here because
     * language does not necessarily fit in a Short-integer. */
    language = wsp_string_to_language(value);
    if (language >= 0)
        wsp_pack_integer_value(packed, language);
    else
        wsp_pack_text(packed, value);
    return 0;
}

/* Encode value as Well-known-method | Token-text */
static int pack_method(Octstr *packed, Octstr *value)
{
    /* In the future, we will need some way to refer to extended
     * method names negotiated for this session. */ 
    return wsp_pack_constrained_value(packed, value,
                                      wsp_string_to_method(value));
}

/* Encode value as Accept-ranges-value */
static int pack_range_unit(Octstr *packed, Octstr *value)
{
    return wsp_pack_constrained_value(packed, value,
                                      wsp_string_to_ranges(value));
}

/* Encode byte-range-spec | suffix-byte-range-spec as Range-value.
 * Return position after the parsed spec. */
static int pack_range_value(Octstr *packed, Octstr *value, long pos)
{
    long first_byte_pos;
    long last_byte_pos;
    long suffix_length;
    Octstr *encoding;

    while (isspace(octstr_get_char(value, pos)))
        pos++;

    if (isdigit(octstr_get_char(value, pos))) {
        /* byte-range-spec */
        pos = octstr_parse_long(&first_byte_pos, value, pos, 10);
        if (pos < 0 || first_byte_pos < 0)
            return -1;

        while (isspace(octstr_get_char(value, pos)))
            pos++;

        if (octstr_get_char(value, pos) != '-')
            return -1;
        pos++;

        while (isspace(octstr_get_char(value, pos)))
            pos++;

        if (isdigit(octstr_get_char(value, pos))) {
            pos = octstr_parse_long(&last_byte_pos, value, pos, 10);
            if (pos < 0 || last_byte_pos < 0)
                return -1;
        } else {
            last_byte_pos = -1;
        }

        encoding = octstr_create("");
        octstr_append_char(encoding, BYTE_RANGE);
        octstr_append_uintvar(encoding, first_byte_pos);
        if (last_byte_pos >= 0)
            octstr_append_uintvar(encoding, last_byte_pos);
        wsp_pack_value(packed, encoding);
        octstr_destroy(encoding);
    } else if (octstr_get_char(value, pos) == '-') {
        /* suffix-byte-range-spec */
        pos++;

        pos = octstr_parse_long(&suffix_length, value, pos, 10);
        if (pos < 0 || suffix_length < 0)
            return -1;

        encoding = octstr_create("");
        octstr_append_char(encoding, SUFFIX_BYTE_RANGE);
        octstr_append_uintvar(encoding, suffix_length);
        wsp_pack_value(packed, encoding);
        octstr_destroy(encoding);
    } else
        return -1;

    return pos;
}

static int pack_transfer_encoding(Octstr *packed, Octstr *value)
{
    return wsp_pack_constrained_value(packed, value,
                                      wsp_string_to_transfer_encoding(value));
}

/* Also used by pack_content_type  */
static int pack_accept(Octstr *packed, Octstr *value)
{
    List *parms;
    long media;

    parms = wsp_strip_parameters(value);
    /* XXX we need to obey which WSP encoding-version to use */
    /* media = wsp_string_to_content_type(value); */
    media = wsp_string_to_versioned_content_type(value, WSP_1_2);

    /* See if we can fit this in a Constrained-media encoding */
    if (parms == NULL && media <= MAX_SHORT_INTEGER) {
        wsp_pack_constrained_value(packed, value, media);
    } else {
        Octstr *encoding = octstr_create("");

        if (media >= 0)
            wsp_pack_integer_value(encoding, media);
        else
            wsp_pack_text(encoding, value);

        wsp_pack_parameters(encoding, parms);
        wsp_pack_value(packed, encoding);
        octstr_destroy(encoding);
    }

    gwlist_destroy(parms, parm_destroy_item);
    return 0;
}

static int pack_accept_charset(Octstr *packed, Octstr *value)
{
    List *parms;
    long charset;
    long qvalue;

    parms = wsp_strip_parameters(value);
    charset = wsp_string_to_charset(value);
    qvalue = 1000;
    if (parms)
        qvalue = get_qvalue(parms, qvalue);
    gwlist_destroy(parms, parm_destroy_item);

    /* See if we can fit this in a Constrained-charset encoding */
    if (qvalue == 1000 && charset <= MAX_SHORT_INTEGER) {
        wsp_pack_constrained_value(packed, value, charset);
    } else {
        Octstr *encoding = octstr_create("");

        if (charset >= 0)
            wsp_pack_integer_value(encoding, charset);
        else
            wsp_pack_text(encoding, value);

        if (qvalue != 1000)
            pack_qvalue(encoding, qvalue);
        wsp_pack_value(packed, encoding);
        octstr_destroy(encoding);
    }

    return 0;
}

/* WSP 8.4.2.9 does not specify any way to encode parameters for
 * an Accept-encoding field, but RFC2616 allows q parameters.
 * We'll have to simplify: qvalue > 0 means encode it, qvalue == 0
 * means don't encode it.
 */
static int pack_accept_encoding(Octstr *packed, Octstr *value)
{
    List *parms;
    int qvalue;

    qvalue = 1000;   /* default */

    parms = wsp_strip_parameters(value);
    if (parms)
        qvalue = get_qvalue(parms, qvalue);
    gwlist_destroy(parms, parm_destroy_item);

    if (qvalue > 0) {
        if (qvalue < 1000)
            warning(0, "Cannot encode q-value in Accept-Encoding.");
        pack_encoding(packed, value);
    } else {
        warning(0, "Cannot encode q=0 in Accept-Encoding; skipping this encoding.");
        return -1;
    }

    return 0;
}

static int pack_accept_language(Octstr *packed, Octstr *value)
{
    List *parms;
    long language;
    long qvalue;

    parms = wsp_strip_parameters(value);
    language = wsp_string_to_language(value);
    qvalue = 1000;
    if (parms)
        qvalue = get_qvalue(parms, qvalue);
    gwlist_destroy(parms, parm_destroy_item);

    /* See if we can fit this in a Constrained-language encoding. */
    /* Note that our language table already includes Any-language */
    if (qvalue == 1000 && language <= MAX_SHORT_INTEGER) {
        wsp_pack_constrained_value(packed, value, language);
    } else {
        Octstr *encoding = octstr_create("");

        if (language >= 0)
            wsp_pack_integer_value(encoding, language);
        else
            wsp_pack_text(encoding, value);

        if (qvalue != 1000)
            pack_qvalue(encoding, qvalue);
        wsp_pack_value(packed, encoding);
        octstr_destroy(encoding);
    }

    return 0;
}

static int pack_cache_control(Octstr *packed, Octstr *value)
{
    Parameter *parm;
    long tmp;

    parm = parm_parse(value);

    if (parm->value == NULL) {
        wsp_pack_constrained_value(packed, value,
                                   wsp_string_to_cache_control(parm->key));
    } else {
        Octstr *encoding = octstr_create("");

        tmp = wsp_string_to_cache_control(parm->key);
        if (tmp < 0) {
            /* According to WSP 8.4.2.15, the format
             * is "Cache-extension Parameter", and
             * Cache-extension is a Token-text.
             * But in HTTP a cache-extension is of
             * the form token=value, which maps
             * nicely to Parameter.  So what is
             * this extra Token-text?  I decided to leave it blank.
             *  - Richard Braakman
             */
            wsp_pack_text(encoding, octstr_imm(""));
            pack_parameter(encoding, parm);
        } else {
            int done = 0;
            Octstr *value_encoding;
            List *names;
            Octstr *element;

            value_encoding = octstr_create("");
            switch (tmp) {
            case WSP_CACHE_CONTROL_NO_CACHE:
            case WSP_CACHE_CONTROL_PRIVATE:
                if (octstr_get_char(parm->value, 0) == '"')
                    octstr_delete(parm->value, 0, 1);
                if (octstr_get_char(parm->value, octstr_len(parm->value) - 1) == '"')
                    octstr_delete(parm->value, octstr_len(parm->value) - 1, 1);
                names = http_header_split_value(parm->value);
                while ((element = gwlist_consume(names))) {
                    pack_field_name(value_encoding, element);
                    octstr_destroy(element);
                }
                gwlist_destroy(names, octstr_destroy_item);
                done = 1;
                break;

            case WSP_CACHE_CONTROL_MAX_AGE:
            case WSP_CACHE_CONTROL_MAX_STALE:
            case WSP_CACHE_CONTROL_MIN_FRESH:
                if (wsp_pack_integer_string(value_encoding, parm->value) >= 0)
                    done = 1;
                break;
            }

            if (done) {
                wsp_pack_short_integer(encoding, tmp);
                octstr_append(encoding, value_encoding);
            } else {
                /* See note above */
                pack_parameter(encoding, parm);
            }
            octstr_destroy(value_encoding);
        }

        wsp_pack_value(packed, encoding);
        octstr_destroy(encoding);
    }

    parm_destroy(parm);
    return 1;
}

static int pack_content_disposition(Octstr *packed, Octstr *value)
{
    List *parms;
    long disposition;

    parms = wsp_strip_parameters(value);
    disposition = wsp_string_to_disposition(value);

    if (disposition >= 0) {
        Octstr *encoding = octstr_create("");

        wsp_pack_short_integer(encoding, disposition);
        wsp_pack_parameters(encoding, parms);
        wsp_pack_value(packed, encoding);
        octstr_destroy(encoding);
    } else {
        warning(0, "WSP: Cannot encode Content-Disposition '%s'.",
                octstr_get_cstr(value));
        goto error;
    }

    gwlist_destroy(parms, parm_destroy_item);
    return 0;

error:
    gwlist_destroy(parms, parm_destroy_item);
    return -1;
}

static int pack_content_range(Octstr *packed, Octstr *value)
{
    Octstr *bytes;
    long pos;
    long firstbyte, lastbyte, instancelen;
    Octstr *encoding;

    bytes = octstr_imm("bytes ");
    if (octstr_ncompare(value, bytes, octstr_len(bytes)) != 0)
        goto error;

    pos = octstr_len(bytes);
    while (isspace(octstr_get_char(value, pos)))
        pos++;
    if (octstr_get_char(value, pos) == '*')
        goto warning;
    pos = octstr_parse_long(&firstbyte, value, pos, 10);
    if (pos < 0)
        goto error;

    while (isspace(octstr_get_char(value, pos)))
        pos++;
    if (octstr_get_char(value, pos++) != '-')
        goto error;

    pos = octstr_parse_long(&lastbyte, value, pos, 10);
    if (pos < 0)
        goto error;

    while (isspace(octstr_get_char(value, pos)))
        pos++;
    if (octstr_get_char(value, pos++) != '/')
        goto error;

    while (isspace(octstr_get_char(value, pos)))
        pos++;
    if (octstr_get_char(value, pos) == '*')
        goto warning;
    pos = octstr_parse_long(&instancelen, value, pos, 10);
    if (pos < 0)
        goto error;

    /* XXX: If the range is valid but not representable,
     * or if it's invalid, then should we refrain from sending
     * anything at all?  It might pollute the client's cache. */

    if (lastbyte < firstbyte || instancelen < lastbyte) {
        warning(0, "WSP: Content-Range '%s' is invalid.",
                octstr_get_cstr(value));
        return -1;
    }

    encoding = octstr_create("");
    octstr_append_uintvar(encoding, firstbyte);
    octstr_append_uintvar(encoding, instancelen);
    wsp_pack_value(packed, encoding);
    octstr_destroy(encoding);

    return 0;

error:
    warning(0, "WSP: Cannot parse Content-Range '%s'.",
            octstr_get_cstr(value));
    return -1;

warning:
    warning(0, "WSP: Cannot encode Content-Range '%s'.",
            octstr_get_cstr(value));
    return -1;
}

int wsp_pack_content_type(Octstr *packed, Octstr *value)
{
    /* The expansion of Content-type-value works out to be
     * equivalent to Accept-value. */ 
    return pack_accept(packed, value);
}

static int pack_expires(Octstr *packed, Octstr *value)
{
    int ret;

    ret = wsp_pack_date(packed, value);

    if (ret < 0) {
	/* Responses with an invalid Expires header should be treated
	as already expired.  If we just skip this header, then the client
	won't know that.  So we encode one with a date far in the past. */
	wsp_pack_long_integer(packed, LONG_AGO_VALUE);
	ret = 0;
    }

    return ret;
}

static int pack_if_range(Octstr *packed, Octstr *value)
{
    if (octstr_get_char(value, 0) == '"' ||
        (octstr_get_char(value, 0) == 'W' &&
         octstr_get_char(value, 1) == '/')) {
        return wsp_pack_quoted_text(packed, value);   /* It's an etag */
    } else {
        return wsp_pack_date(packed, value);
    }
}

static int pack_pragma(Octstr *packed, Octstr *value)
{
    Octstr *nocache;

    nocache = octstr_imm("no-cache");
    if (octstr_case_compare(value, nocache) == 0)
        wsp_pack_short_integer(packed, WSP_CACHE_CONTROL_NO_CACHE);
    else {
        Parameter *parm;
        Octstr *encoding;

        encoding = octstr_create("");
        parm = parm_parse(value);
        pack_parameter(encoding, parm);
        wsp_pack_value(packed, encoding);
        octstr_destroy(encoding);
        parm_destroy(parm);
    }

    return 0;
}

static int pack_range(Octstr *packed, Octstr *value)
{
    Octstr *bytes = octstr_imm("bytes");
    long pos;

    if (octstr_ncompare(value, bytes, octstr_len(bytes)) != 0
        || is_token_char(octstr_get_char(value, octstr_len(bytes))))
        goto error;

    pos = octstr_len(bytes);
    while (isspace(octstr_get_char(value, pos)))
        pos++;

    if (octstr_get_char(value, pos) != '=')
        goto error;
    pos++;

    for (;;) {
        /* Discard the whole header if any part of it can't be
         * parsed.  Probably a partial Range header is worse
         * than none at all. */
        pos = pack_range_value(packed, value, pos);
        if (pos < 0)
            goto error;

        while (isspace(octstr_get_char(value, pos)))
            pos++;

        if (octstr_get_char(value, pos) != ',')
            break;
        pos++;

        wsp_pack_short_integer(packed, WSP_HEADER_RANGE);
    }

    return 0;

error:
    warning(0, "WSP: Cannot parse 'Range: %s'.",
            octstr_get_cstr(value));
    return -1;
}

/* The value is either a HTTP-date or a delta-seconds (integer). */
int wsp_pack_retry_after(Octstr *packed, Octstr *value)
{
    Octstr *encoded = NULL;

    encoded = octstr_create("");
    if (isdigit(octstr_get_char(value, 0))) {
        octstr_append_char(encoded, RELATIVE_TIME);
        if (wsp_pack_integer_string(encoded, value) < 0)
            goto error;
    } else {
        octstr_append_char(encoded, ABSOLUTE_TIME);
        if (wsp_pack_date(encoded, value) < 0)
            goto error;
    }
    wsp_pack_value(packed, encoded);

    octstr_destroy(encoded);
    return 0;

error:
    octstr_destroy(encoded);
    return -1;
}


static int convert_rfc2616_warning_to_rfc2068(int warn_code)
{
    int i;
    struct {
	int rfc2616code;
	int rfc2068code;
    } code_transform[] = {
	{ 110, 10 },  /* Response is stale */
	{ 111, 11 },  /* Revalidation failed */
	{ 112, 12 },  /* Disconnected operation */
	{ 113, 13 },  /* Heuristic expiration */
	{ 199, 99 },  /* Miscellaneous warning */
	{ 214, 14 },  /* Transformation applied */
	{ 299, 99 },  /* Miscellaneous (persistent) warning */
	{ -1, -1 }
    };
    
    for (i = 0; code_transform[i].rfc2616code >= 0; i++) {
	if (code_transform[i].rfc2616code == warn_code)
	    return code_transform[i].rfc2068code;
    }

    return warn_code; /* conversion failed */
}

static int pack_warning(Octstr *packed, Octstr *value)
{
    long warn_code = -1;
    Octstr *warn_agent = NULL;
    Octstr *warn_text = NULL;
    long pos;
    long start;

    pos = octstr_parse_long(&warn_code, value, 0, 10);
    if (pos < 0 || warn_code < 0)
        goto error;

    if (warn_code > 99) {
	/* RFC2068 uses 2-digit codes, and RFC2616 uses 3-digit codes.
	 * This must be an RFC2616 code.  We don't have room in the
	 * encoding for such codes, so we try to convert it back to
	 * an RFC2068 code. */
	warn_code = convert_rfc2616_warning_to_rfc2068(warn_code);
    }

    if (warn_code > MAX_SHORT_INTEGER) {
	warning(0, "WSP: Cannot encode warning code %ld.", warn_code);
	return -1;
    }

    while (isspace(octstr_get_char(value, pos)))
        pos++;

    start = pos;
    while (pos < octstr_len(value) && !isspace(octstr_get_char(value, pos)))
        pos++;

    if (pos > start)
        warn_agent = octstr_copy(value, start, pos - start);

    while (isspace(octstr_get_char(value, pos)))
        pos++;

    start = pos;
    pos += http_header_quoted_string_len(value, pos);
    if (pos > start)
        warn_text = octstr_copy(value, start, pos - start);

    if (warn_agent == NULL && warn_text == NULL) {
        /* Simple encoding */
        wsp_pack_short_integer(packed, warn_code);
    } else {
        /* General encoding */
        Octstr *encoding = octstr_create("");

        if (warn_agent == NULL)
            warn_agent = octstr_create("");
        if (warn_text == NULL)
            warn_text = octstr_create("");

        wsp_pack_short_integer(encoding, warn_code);
        wsp_pack_text(encoding, warn_agent);
        wsp_pack_text(encoding, warn_text);
        wsp_pack_value(packed, encoding);
        octstr_destroy(encoding);
    }

    octstr_destroy(warn_agent);
    octstr_destroy(warn_text);
    return 0;

error:
    warning(0, "WSP: Cannot parse 'Warning: %s'.",
            octstr_get_cstr(value));
    octstr_destroy(warn_agent);
    octstr_destroy(warn_text);
    return -1;
}

void wsp_pack_separate_content_type(Octstr *packed, List *headers)
{
    Octstr *content_type;

    /* Can't use http_header_get_content_type because it
     * does not parse all possible parameters. */
    content_type = http_header_find_first(headers, "Content-Type");

    if (content_type == NULL) {
        warning(0, "WSP: Missing Content-Type header in "
                "response, guessing application/octet-stream");
        content_type = octstr_create("application/octet-stream");
    }
    octstr_strip_blanks(content_type);
    wsp_pack_content_type(packed, content_type);
    octstr_destroy(content_type);
}

int wsp_pack_list(Octstr *packed, long fieldnum, List *elements, int i)
{
    long startpos;
    Octstr *element;

    while ((element = gwlist_consume(elements))) {
        startpos = octstr_len(packed);

        wsp_pack_short_integer(packed, fieldnum);
        if (headerinfo[i].func(packed, element) < 0) {
            /* Remove whatever we added */
            octstr_delete(packed, startpos,
                          octstr_len(packed) - startpos);
            /* But continue processing elements */
        }
        octstr_destroy(element);
    }
    return 0;
}

static int pack_known_header(Octstr *packed, long fieldnum, Octstr *value)
{
    List *elements = NULL;
    long startpos;
    long i;

    octstr_strip_blanks(value);

    startpos = octstr_len(packed);

    for (i = 0; i < TABLE_SIZE(headerinfo); i++) {
        if (headerinfo[i].header == fieldnum)
            break;
    }

    if (i == TABLE_SIZE(headerinfo)) {
        error(0, "WSP: Do not know how to encode header type %ld",
              fieldnum);
        goto error;
    }

    if (headerinfo[i].allows_list == LIST)
        elements = http_header_split_value(value);
    else if (headerinfo[i].allows_list == BROKEN_LIST)
        elements = http_header_split_auth_value(value);
    else
        elements = NULL;

    if (elements != NULL) {
        if (wsp_pack_list(packed, fieldnum, elements, i) < 0)
            goto error;
    } else {
        wsp_pack_short_integer(packed, fieldnum);
        debug("wsp",0,"WSP: Known header 0x%04lx, value `%s'.",
              fieldnum, octstr_get_cstr(value));
        if (headerinfo[i].func(packed, value) < 0)
            goto error;
    }

    gwlist_destroy(elements, octstr_destroy_item);
    return 0;

error:
    /* Remove whatever we added */
    octstr_delete(packed, startpos, octstr_len(packed) - startpos);
    gwlist_destroy(elements, octstr_destroy_item);
    return -1;
}

int wsp_pack_application_header(Octstr *packed,
                                   Octstr *fieldname, Octstr *value)
{
    if (!is_token(fieldname)) {
        warning(0, "WSP headers: `%s' is not a valid HTTP token.",
                octstr_get_cstr(fieldname));
        return -1;
    }

    /* We have to deal specially with the X-WAP.TOD header, because it
     * is the only case of a text-format header defined with a non-text
     * field value. */
    /* Normally this should be a case-insensitive comparison, but this
     * header will only be present if we generated it ourselves in the
     * application layer. */
    if (octstr_str_compare(fieldname, "X-WAP.TOD") == 0) {
        wsp_pack_text(packed, fieldname);
        return wsp_pack_date(packed, value);
    }

    wsp_pack_text(packed, fieldname);
    wsp_pack_text(packed, value);
    return 0;
}

Octstr *wsp_headers_pack(List *headers, int separate_content_type, int wsp_version)
{
    Octstr *packed;
    long i, len;
    int errors;

    packed = octstr_create("");
    if (separate_content_type)
        wsp_pack_separate_content_type(packed, headers);

    len = gwlist_len(headers);
    for (i = 0; i < len; i++) {
        Octstr *fieldname;
        Octstr *value;
        long fieldnum;

        http_header_get(headers, i, &fieldname, &value);
        /* XXX we need to obey which WSP encoding-version to use */
        /* fieldnum = wsp_string_to_header(fieldname); */
        fieldnum = wsp_string_to_versioned_header(fieldname, wsp_version);

        errors = 0;

        if (separate_content_type && fieldnum == WSP_HEADER_CONTENT_TYPE) {
	    	/* already handled */
        	debug("wsp",0,"WSP: content type already handled.");
        } else if (fieldnum < 0) {
            if (wsp_pack_application_header(packed, fieldname, value) < 0)
                errors = 1;
        } else {
            if (pack_known_header(packed, fieldnum, value) < 0)
                errors = 1;
        }

        if (errors)
            warning(0, "Skipping header: %s: %s",
                    octstr_get_cstr(fieldname),
                    octstr_get_cstr(value));

        octstr_destroy(fieldname);
        octstr_destroy(value);
    }

    /*
    http_header_dump(headers);
    octstr_dump(packed, 0);
    */

    return packed;
}

