/*
 *
 *  Multimedia Messaging Service
 *
 *  Copyright (C) 2010-2011  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>

#include <glib.h>

#include "wsputil.h"

struct wsp_hex_str_entry {
	unsigned int type;
	const char *type_str;
};

/*
 * http://www.wapforum.org/wina/wsp-content-type.htm
 */
static const char *content_types[] = {
	"*/*",
	"text/*",
	"text/html",
	"text/plain",
	"text/x-hdml",
	"text/x-ttml",
	"text/x-vCalendar",
	"text/x-vCard",
	"text/vnd.wap.wml",
	"text/vnd.wap.wmlscript",
	"text/vnd.wap.wta-event",
	"multipart/*",
	"multipart/mixed",
	"multipart/form-data",
	"multipart/byterantes",
	"multipart/alternative",
	"application/*",
	"application/java-vm",
	"application/x-www-form-urlencoded",
	"application/x-hdmlc",
	"application/vnd.wap.wmlc",
	"application/vnd.wap.wmlscriptc",
	"application/vnd.wap.wta-eventc",
	"application/vnd.wap.uaprof",
	"application/vnd.wap.wtls-ca-certificate",
	"application/vnd.wap.wtls-user-certificate",
	"application/x-x509-ca-cert",
	"application/x-x509-user-cert",
	"image/*",
	"image/gif",
	"image/jpeg",
	"image/tiff",
	"image/png",
	"image/vnd.wap.wbmp",
	"application/vnd.wap.multipart.*",
	"application/vnd.wap.multipart.mixed",
	"application/vnd.wap.multipart.form-data",
	"application/vnd.wap.multipart.byteranges",
	"application/vnd.wap.multipart.alternative",
	"application/xml",
	"text/xml",
	"application/vnd.wap.wbxml",
	"application/x-x968-cross-cert",
	"application/x-x968-ca-cert",
	"application/x-x968-user-cert",
	"text/vnd.wap.si",
	"application/vnd.wap.sic",
	"text/vnd.wap.sl",
	"application/vnd.wap.slc",
	"text/vnd.wap.co",
	"application/vnd.wap.coc",
	"application/vnd.wap.multipart.related",
	"application/vnd.wap.sia",
	"text/vnd.wap.connectivity-xml",
	"application/vnd.wap.connectivity-wbxml",
	"application/pkcs7-mime",
	"application/vnd.wap.hashed-certificate",
	"application/vnd.wap.signed-certificate",
	"application/vnd.wap.cert-response",
	"application/xhtml+xml",
	"application/wml+xml",
	"text/css",
	"application/vnd.wap.mms-message",
	"application/vnd.wap.rollover-certificate",
	"application/vnd.wap.locc+wbxml",
	"application/vnd.wap.loc+xml",
	"application/vnd.syncml.dm+wbxml",
	"application/vnd.syncml.dm+xml",
	"application/vnd.syncml.notification",
	"application/vnd.wap.xhtml+xml",
	"application/vnd.wv.csp.cir",
	"application/vnd.oma.dd+xml",
	"application/vnd.oma.drm.message",
	"application/vnd.oma.drm.content",
	"application/vnd.oma.drm.rights+xml",
	"application/vnd.oma.drm.rights+wbxml",
};

#define LAST_CONTENT_TYPE (sizeof(content_types) / sizeof(const char *))

/*
 * http://www.wapforum.org/wina/push-app-id.htm
 */
static const struct wsp_hex_str_entry app_id[] = {
	{ 0x04, "x-wap-application:mms.ua" },
	{ 0x07, "x-wap-application:syncml.dm" },
	{ 0x08, "x-wap-application:drm.ua" },
	{ 0xFF, NULL }
};

/* http://www.wapforum.org/wina/wsp-content-type.htm */
static const struct wsp_hex_str_entry extension_mimetypes[] = {
	{ 0x0201, "application/vnd.uplanet.cacheop-wbxml" },
	{ 0x0202, "application/vnd.uplanet.signal" },
	{ 0x0203, "application/vnd.uplanet.alert-wbxml" },
	{ 0x0204, "application/vnd.uplanet.list-wbxml" },
	{ 0x0205, "application/vnd.uplanet.listcmd-wbxml" },
	{ 0x0206, "application/vnd.uplanet.channel-wbxml" },
	{ 0x0207, "application/vnd.uplanet.provisioning-status-uri" },
	{ 0x0208, "x-wap.multipart/vnd.uplanet.header-set" },
	{ 0x0209, "application/vnd.uplanet.bearer-choice-wbxml" },
	{ 0x020A, "application/vnd.phonecom.mmc-wbxml" },
	{ 0x020B, "application/vnd.nokia.syncset+wbxml" },
	{ 0x020C, "image/x-up-wpng" },
	{ 0xFFFF, NULL },
};

static const struct wsp_hex_str_entry charset_assignments[] = {
	{ 0x0000, "*" },
	{ 0x07EA, "big5" },
	{ 0x03E8, "iso-10646-ucs-2" },
	{ 0x0004, "iso-8859-1" },
	{ 0x0005, "iso-8859-2" },
	{ 0x0006, "iso-8859-3" },
	{ 0x0007, "iso-8859-4" },
	{ 0x0008, "iso-8859-5" },
	{ 0x0009, "iso-8859-6" },
	{ 0x000A, "iso-8859-7" },
	{ 0x000B, "iso-8859-8" },
	{ 0x000C, "iso-8859-9" },
	{ 0x0011, "shift_JIS" },
	{ 0x0003, "us-ascii" },
	{ 0x006A, "utf-8" },
	{ 0x03F7, "utf-16" },
	{ 0xFFFF, NULL },
};

/*
 * Control Characters 0-8, 10-31 and 127.  The tab character is omitted
 * since it is included in the sep chars array and the most generic TEXT
 * type of RFC 2616 explicitly allows tabs
 */
static const char *ctl_chars = "\x01\x02\x03\x04\x05\x06\x07\x08\x0A"
				"\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14"
				"\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E"
				"\x1F\x7F";

static const char *sep_chars = "()<>@,;:\\\"/[]?={} \t";

static const char *decode_text_common(const unsigned char *pdu,
					unsigned int len,
					gboolean filter_ctl,
					gboolean filter_sep,
					unsigned int *consumed)
{
	unsigned char *c;

	c = memchr(pdu, '\0', len);

	if (c == NULL)
		return NULL;

	c += 1;

	/* RFC 2616 Section 2.2 */
	if (filter_ctl && strpbrk((const char *) pdu, ctl_chars) != NULL)
		return NULL;

	if (filter_sep && strpbrk((const char *) pdu, sep_chars) != NULL)
		return NULL;

	if (consumed)
		*consumed = c - pdu;

	return (const char *) pdu;
}

const char *wsp_decode_token_text(const unsigned char *pdu, unsigned int len,
					unsigned int *consumed)
{
	return decode_text_common(pdu, len, TRUE, TRUE, consumed);
}

const char *wsp_decode_text(const unsigned char *pdu, unsigned int len,
					unsigned int *consumed)
{
	const char *r;
	unsigned int fudge = 0;

	if (*pdu == 127) {
		pdu++;

		if (*pdu < 128)
			return NULL;

		len -= 1;
		fudge += 1;
	}

	r = decode_text_common(pdu, len, TRUE, FALSE, consumed);

	if (consumed)
		*consumed += fudge;

	return r;
}

const char *wsp_decode_quoted_string(const unsigned char *pdu, unsigned int len,
					unsigned int *consumed)
{
	const char *text;

	text = wsp_decode_text(pdu, len, consumed);
	if (text == NULL)
		return NULL;

	if (*text != '"')
		return NULL;

	/* Skip initial quote */
	text++;

	return text;
}

gboolean wsp_decode_uintvar(const unsigned char *pdu, unsigned int len,
				unsigned int *out_len, unsigned int *consumed)
{
	unsigned int var;
	unsigned int i;
	unsigned int cont;

	for (i = 0, var = 0, cont = TRUE; i < 5 && i < len && cont; i++) {
		cont = pdu[i] & 0x80;
		var = (var << 7) | (pdu[i] & 0x7f);
	}

	if (cont)
		return FALSE;

	if (out_len)
		*out_len = var;

	if (consumed)
		*consumed = i;

	return TRUE;
}

gboolean wsp_decode_integer(const unsigned char *pdu, unsigned int len,
				unsigned int *out_val, unsigned int *consumed)
{
	unsigned int var;
	unsigned int i;
	unsigned int count;

	if (pdu[0] & 0x80) {
		var = pdu[0] & 0x7f;
		count = 1;
	} else if (pdu[0] <= 30) {
		unsigned int value_len = *pdu;
		if (value_len > (len - 1))
			return FALSE;

		if (value_len > sizeof(unsigned int))
			return FALSE;

		var = 0;
		for (i = 0; i < value_len; i++)
			var = (var << 8) | pdu[i + 1];
		count = value_len + 1;
	} else
		return FALSE;

	if (out_val)
		*out_val = var;

	if (consumed)
		*consumed = count;

	return TRUE;
}

gboolean wsp_decode_field(const unsigned char *pdu, unsigned int max,
					enum wsp_value_type *out_type,
					const void **out_value,
					unsigned int *out_len,
					unsigned int *out_read)
{
	const unsigned char *end = pdu + max;
	const unsigned char *begin = pdu;
	unsigned int len;
	enum wsp_value_type value;
	unsigned int consumed;

	if (*pdu <= 30) {
		len = *pdu;
		pdu++;

		if (pdu + len > end)
			return FALSE;

		value = WSP_VALUE_TYPE_LONG;
	} else if (*pdu >= 128) {
		len = 1;
		value = WSP_VALUE_TYPE_SHORT;
	} else if (*pdu == 31) {
		pdu++;

		if (pdu == end)
			return FALSE;

		if (wsp_decode_uintvar(pdu, end - pdu,
						&len, &consumed) == FALSE)
			return FALSE;

		pdu += consumed;

		if (pdu + len > end)
			return FALSE;

		value = WSP_VALUE_TYPE_LONG;
	} else {
		if (decode_text_common(pdu, end - pdu,
					TRUE, FALSE, &len) == NULL)
			return FALSE;

		value = WSP_VALUE_TYPE_TEXT;
	}

	if (out_type)
		*out_type = value;

	if (out_value)
		*out_value = pdu;

	if (out_len)
		*out_len = len;

	if (out_read)
		*out_read = pdu - begin + len;

	return TRUE;
}

gboolean wsp_get_well_known_content_type(const char *text,
							unsigned int *out_val)
{
	unsigned int i;

	for (i = 0; i < LAST_CONTENT_TYPE; i++) {
		if (g_str_equal(text, content_types[i]) == TRUE) {
			*out_val = i;
			return TRUE;
		}
	}

	return FALSE;
}

gboolean wsp_get_well_known_charset(const char *text, unsigned int *out_val)
{
	unsigned int i;

	for (i = 0; charset_assignments[i].type_str != NULL; i++) {
		if (g_str_equal(charset_assignments[i].type_str,
							text) == TRUE) {
			*out_val = charset_assignments[i].type;
			return TRUE;
		}
	}

	return FALSE;
}

static const char *get_text_entry(unsigned int value,
					const struct wsp_hex_str_entry *table)
{
	unsigned int i;

	for (i = 0; table[i].type_str != NULL; i++) {
		if (table[i].type == value)
			return table[i].type_str;
	}

	return NULL;
}

gboolean wsp_decode_content_type(const unsigned char *pdu, unsigned int max,
						const void **out_value,
						unsigned int *out_read,
						unsigned int *out_param_len)
{
	unsigned int param_len = 0;
	unsigned int len;
	const void *data;
	enum wsp_value_type value_type;
	unsigned int consumed;

	if (wsp_decode_field(pdu, max, &value_type, &data,
						&len, &consumed) != TRUE)
		return FALSE;

	if (value_type == WSP_VALUE_TYPE_LONG) {
		unsigned int media_len;
		unsigned int value_len;

		if (wsp_decode_field(data, max, &value_type, &data,
						&value_len, &media_len) != TRUE)
			return FALSE;

		param_len = len - media_len;
		consumed -= param_len;

		/* Handle Well-Known-Media Long-Integer case */
		if (value_type == WSP_VALUE_TYPE_LONG) {
			const unsigned char *pdu_val = data;
			unsigned int var = 0;
			unsigned int i;

			if (value_len > sizeof(unsigned int))
				return FALSE;

			for (i = 0; i < value_len; i++)
				var = (var << 8) | pdu_val[i + 1];

			data = get_text_entry(var, extension_mimetypes);
		}
	}

	if (value_type == WSP_VALUE_TYPE_SHORT) {
		const unsigned char *pdu_val = data;
		unsigned int val;

		val = *pdu_val & 0x7f;

		if (val >= LAST_CONTENT_TYPE)
			return FALSE;

		data = content_types[val];
	}

	if (out_value)
		*out_value = data;

	if (out_read)
		*out_read = consumed;

	if (out_param_len)
		*out_param_len = param_len;

	return TRUE;
}

gboolean wsp_decode_application_id(struct wsp_header_iter *iter,
							const void **out_value)
{
	const unsigned char *pdu_val = wsp_header_iter_get_val(iter);
	unsigned int val;
	unsigned int val_len;
	unsigned int i;

	switch (wsp_header_iter_get_val_type(iter)) {
	case WSP_VALUE_TYPE_TEXT:
		if (out_value)
			*out_value = pdu_val;

		break;

	/*
	 * Well-known field values MUST be encoded using the
	 * compact binary formats
	 */
	case WSP_VALUE_TYPE_SHORT:
		val = *pdu_val & 0x7f;

		if (out_value)
			*out_value = get_text_entry(val, app_id);

		break;

	case WSP_VALUE_TYPE_LONG:
		val_len = wsp_header_iter_get_val_len(iter);

		if (val_len > 2)
			return FALSE;

		for (i = 0, val = 0; i < val_len && i < sizeof(val); i++)
			val = (val << 8) | pdu_val[i];

		if (out_value)
			*out_value = get_text_entry(val, app_id);

		break;
	}

	return TRUE;
}

static inline gboolean is_content_type_header(const unsigned char *pdu,
						unsigned char code_page,
						unsigned int flags)
{
	/* Check for MMS Content-Type header */
	if (flags & WSP_HEADER_ITER_FLAG_DETECT_MMS_MULTIPART)
		if (code_page == 1 && *pdu == 0x84)
			return TRUE;

	/* Check for WSP default Content-Type header */
	if (code_page == 1 && *pdu == 0x91)
		return TRUE;

	return FALSE;
}

gboolean wsp_encode_uintvar(unsigned int value, unsigned char *dest,
				unsigned int dest_size, unsigned int *written)
{
	unsigned char d[5];
	unsigned int count = 0;

	/* Separate into 7-bit chunks, LS first */
	while (value || !count) {
		d[count++] = value & 0x7F;
		value = value >> 7;
	}

	if (count > dest_size)
		return FALSE;

	*written = count;

	/*
	 * Output to stream, MS first!
	 * 0x80 flag = "continue". LS byte does not have this flag.
	 */
	while (--count)
		*dest++ = d[count] | 0x80;

	*dest = d[count];

	return TRUE;
}

gboolean wsp_encode_value_length(unsigned int len, unsigned char *dest,
				unsigned int dest_size, unsigned int *written)
{
	if (dest_size < 1)
		return FALSE;

	if (len <= 30) {
		*dest = len;
		*written = 1;

		return TRUE;
	}

	/* 31 is escape for variable length int */
	*dest++ = 31;
	dest_size--;

	if (wsp_encode_uintvar(len, dest, dest_size, written) == FALSE)
		return FALSE;

	*written += 1;

	return TRUE;
}

gboolean wsp_encode_integer(unsigned int value, unsigned char *dest,
				unsigned int dest_size, unsigned int *written)
{
	unsigned char moi[sizeof(unsigned int)];
	unsigned int count;

	if (dest_size < 1)
		return FALSE;

	if (value < 0x80) {
		*dest = value | 0x80;
		*written = 1;

		return TRUE;
	}

	for (count = 0; count < sizeof(unsigned int) && value; count++) {
		moi[count] = value & 0xFF;
		value = value >> 8;
	}

	if (count + 1 > dest_size)
		return FALSE;

	*written = count + 1;
	*dest++ = count;

	for (; count > 0; count--)
		*dest++ = moi[count - 1];

	return TRUE;
}

void wsp_header_iter_init(struct wsp_header_iter *iter,
				const unsigned char *pdu, unsigned int len,
				unsigned int flags)
{
	iter->pdu = pdu;
	iter->pos = 0;
	iter->max = len;
	iter->code_page = 1;
	iter->flags = flags;
}

gboolean wsp_header_iter_next(struct wsp_header_iter *iter)
{
	const unsigned char *pdu = iter->pdu + iter->pos;
	const unsigned char *end = iter->pdu + iter->max;
	enum wsp_header_type header;
	const void *hdr;
	unsigned int consumed;

	if (pdu == end)
		return FALSE;

	/*
	 * 8.4.2.6 Header
	 * The following rules are used to encode headers.
	 * Header = Message-header | Shift-sequence
	 * Shift-sequence = (Shift-delimiter Page-identity) |
	 *					Short-cut-shift-delimiter
	 * Shift-delimiter = <Octet 127>
	 * Page-identity = <Any octet 1-255>
	 * Short-cut-shift-delimiter = <Any octet 1-31>
	 * Message-header = Well-known-header | Application-header
	 * Well-known-header = Well-known-field-name Wap-value
	 * Application-header = Token-text Application-specific-value
	 * Well-known-field-name = Short-integer
	 * Application-specific-value = Text-string
	 */
	while (*pdu == 127 || (*pdu >= 1 && *pdu <= 31)) {
		if (iter->flags & WSP_HEADER_ITER_FLAG_REJECT_CP)
			return FALSE;

		if (*pdu == 127) {
			pdu++;

			if (pdu == end)
				return FALSE;

			iter->code_page = *pdu;
			pdu++;
		} else
			iter->code_page = *pdu++;
	}

	if (pdu == end)
		return FALSE;

	if (*pdu >= 0x80) {
		if (is_content_type_header(pdu, iter->code_page, iter->flags))
			return FALSE;

		header = WSP_HEADER_TYPE_WELL_KNOWN;
		hdr = pdu;
		pdu++;
	} else {
		if (wsp_decode_token_text(pdu, end - pdu, &consumed) == NULL)
			return FALSE;

		header = WSP_HEADER_TYPE_APPLICATION;
		hdr = pdu;
		pdu += consumed;
	}

	if (pdu == end)
		return FALSE;

	/*
	 * Section 8.4.1.2 of WAP-230:
	 * If the field name is encoded in text format, textual values MUST
	 * be used.
	 */
	if ((*pdu < 32 || *pdu > 127) && header == WSP_HEADER_TYPE_APPLICATION)
		return FALSE;

	if (wsp_decode_field(pdu, end - pdu, &iter->value_type,
				&iter->value, &iter->len, &consumed) == FALSE)
		return FALSE;

	iter->header_type = header;
	iter->header = hdr;

	iter->pos = pdu + consumed - iter->pdu;

	return TRUE;
}

unsigned char wsp_header_iter_get_code_page(struct wsp_header_iter *iter)
{
	return iter->code_page;
}

gboolean wsp_header_iter_at_end(struct wsp_header_iter *iter)
{
	if (iter->pos == iter->max)
		return TRUE;

	return FALSE;
}

gboolean wsp_header_iter_is_multipart(struct wsp_header_iter *iter)
{
	const unsigned char *pdu = iter->pdu + iter->pos;

	return is_content_type_header(pdu, iter->code_page, iter->flags);
}

enum wsp_header_type wsp_header_iter_get_hdr_type(struct wsp_header_iter *iter)
{
	return iter->header_type;
}

const unsigned char *wsp_header_iter_get_pdu(struct wsp_header_iter *iter)
{
	return iter->pdu;
}

const void *wsp_header_iter_get_hdr(struct wsp_header_iter *iter)
{
	return iter->header;
}

enum wsp_value_type wsp_header_iter_get_val_type(struct wsp_header_iter *iter)
{
	return iter->value_type;
}

const void *wsp_header_iter_get_val(struct wsp_header_iter *iter)
{
	return iter->value;
}

unsigned int wsp_header_iter_get_val_len(struct wsp_header_iter *iter)
{
	return iter->len;
}

gboolean wsp_multipart_iter_init(struct wsp_multipart_iter *mi,
					struct wsp_header_iter *hi,
					const void **out_content_type,
					unsigned int *out_content_type_len)
{
	const unsigned char *pdu = hi->pdu + hi->pos;
	const unsigned char *end = hi->pdu + hi->max;
	unsigned int consumed;
	unsigned int ct_len;

	/* Assume content-type header is well known */
	if (pdu + 1 > end)
		return FALSE;

	pdu++;

	/* Consume the Content-Type value of Content-Type header */
	if (wsp_decode_field(pdu, end - pdu,
				NULL, NULL, NULL, &consumed) == FALSE)
		return FALSE;

	pdu += consumed;
	ct_len = consumed;

	/*
	 * Consume the uinvar specifying the number of parts.  This is set to
	 * 0 in later specifications and can be safely ignored
	 */
	if (wsp_decode_uintvar(pdu, end - pdu, NULL, &consumed) == FALSE)
		return FALSE;

	memset(mi, 0, sizeof(*mi));
	mi->pdu = hi->pdu + hi->pos;
	mi->max = hi->max - hi->pos;
	mi->pos = pdu + consumed - mi->pdu;

	if (out_content_type)
		*out_content_type = mi->pdu + 1;

	if (out_content_type_len)
		*out_content_type_len = ct_len;

	return TRUE;
}

gboolean wsp_multipart_iter_next(struct wsp_multipart_iter *mi)
{
	const unsigned char *pdu = mi->pdu + mi->pos;
	const unsigned char *end = mi->pdu + mi->max;
	unsigned int headers_len;
	unsigned int body_len;
	unsigned int consumed;

	if (wsp_decode_uintvar(pdu, end - pdu,
				&headers_len, &consumed) == FALSE)
		return FALSE;

	pdu += consumed;

	if (wsp_decode_uintvar(pdu, end - pdu, &body_len, &consumed) == FALSE)
		return FALSE;

	pdu += consumed;

	if (pdu + headers_len + body_len > end)
		return FALSE;

	/* Consume the Content-Type value */
	if (wsp_decode_field(pdu, end - pdu,
				NULL, NULL, NULL, &consumed) == FALSE)
		return FALSE;

	mi->content_type = pdu;
	mi->content_type_len = consumed;
	mi->headers = pdu + consumed;
	mi->headers_len = headers_len - consumed;
	mi->body = pdu + headers_len;
	mi->body_len = body_len;

	mi->pos = pdu - mi->pdu + headers_len + body_len;

	return TRUE;
}

const void *wsp_multipart_iter_get_content_type(struct wsp_multipart_iter *mi)
{
	return mi->content_type;
}

unsigned int wsp_multipart_iter_get_content_type_len(
						struct wsp_multipart_iter *mi)
{
	return mi->content_type_len;
}

const void *wsp_multipart_iter_get_hdr(struct wsp_multipart_iter *mi)
{
	return mi->headers;
}

unsigned int wsp_multipart_iter_get_hdr_len(struct wsp_multipart_iter *mi)
{
	return mi->headers_len;
}

const void *wsp_multipart_iter_get_body(struct wsp_multipart_iter *mi)
{
	return mi->body;
}

unsigned int wsp_multipart_iter_get_body_len(struct wsp_multipart_iter *mi)
{
	return mi->body_len;
}

gboolean wsp_multipart_iter_close(struct wsp_multipart_iter *mi,
					struct wsp_header_iter *hi)
{
	if (mi->pos != mi->max)
		return FALSE;

	hi->pos += mi->pos;

	return TRUE;
}

void wsp_parameter_iter_init(struct wsp_parameter_iter *pi,
				const unsigned char *pdu, unsigned int len)
{
	pi->pdu = pdu;
	pi->max = len;
	pi->pos = 0;
}

gboolean wsp_parameter_iter_next(struct wsp_parameter_iter *pi,
					struct wsp_parameter *out)
{
	const unsigned char *pdu = pi->pdu + pi->pos;
	const unsigned char *end = pi->pdu + pi->max;
	unsigned int token;
	unsigned int consumed;
	const char *untyped;
	const char *value;

	/* Well known parameter token */
	if (wsp_decode_integer(pdu, end - pdu, &token, &consumed) == TRUE) {
		pdu += consumed;

		switch (token) {
		case WSP_PARAMETER_TYPE_LEVEL:
		case WSP_PARAMETER_TYPE_DIFFERENCES:
			if (*pdu & 0x80) {
				unsigned int i = *pdu & 0x7f;

				pdu += 1;
				pi->pos = pdu - pi->pdu;

				if (out) {
					out->type = token;
					out->value = WSP_PARAMETER_VALUE_INT;
					out->integer = i;
				}

				return TRUE;
			}

			/* Fall through to the string case */
		case WSP_PARAMETER_TYPE_NAME_DEFUNCT:
		case WSP_PARAMETER_TYPE_FILENAME_DEFUNCT:
		case WSP_PARAMETER_TYPE_START_DEFUNCT:
		case WSP_PARAMETER_TYPE_START_INFO_DEFUNCT:
		case WSP_PARAMETER_TYPE_COMMENT_DEFUNCT:
		case WSP_PARAMETER_TYPE_DOMAIN_DEFUNCT:
		case WSP_PARAMETER_TYPE_PATH_DEFUNCT:
		case WSP_PARAMETER_TYPE_NAME:
		case WSP_PARAMETER_TYPE_FILENAME:
		case WSP_PARAMETER_TYPE_START:
		case WSP_PARAMETER_TYPE_START_INFO:
		case WSP_PARAMETER_TYPE_COMMENT:
		case WSP_PARAMETER_TYPE_DOMAIN:
		case WSP_PARAMETER_TYPE_PATH:
		case WSP_PARAMETER_TYPE_MAC:
		{
			const char *text = wsp_decode_text(pdu, end - pdu,
								&consumed);

			if (text == NULL)
				return FALSE;

			pdu += consumed;
			pi->pos = pdu - pi->pdu;

			if (out) {
				out->type = token;
				out->value = WSP_PARAMETER_VALUE_TEXT;
				out->text = text;
			}

			return TRUE;
		}

		case WSP_PARAMETER_TYPE_TYPE:
		case WSP_PARAMETER_TYPE_SIZE:
		case WSP_PARAMETER_TYPE_MAX_AGE:
		{
			unsigned int i;

			if (wsp_decode_integer(pdu, end - pdu,
						&i, &consumed) == FALSE)
				return FALSE;

			pdu += consumed;
			pi->pos = pdu - pi->pdu;

			if (out) {
				out->type = token;
				out->value = WSP_PARAMETER_VALUE_INT;
				out->integer = i;
			}

			return TRUE;
		}

		case WSP_PARAMETER_TYPE_PADDING:
		case WSP_PARAMETER_TYPE_SEC:
		{
			if ((*pdu & 0x80) == 0)
				return FALSE;

			pdu += 1;
			pi->pos = pdu - pi->pdu;

			if (out) {
				out->type = token;
				out->value = WSP_PARAMETER_VALUE_INT;
				out->integer = *pdu & 0x7f;
			}

			return TRUE;
		}

		case WSP_PARAMETER_TYPE_CREATION_DATE:
		case WSP_PARAMETER_TYPE_MODIFICATION_DATE:
		case WSP_PARAMETER_TYPE_READ_DATE:
		{
			unsigned int i;

			if (wsp_decode_integer(pdu, end - pdu,
						&i, &consumed) == FALSE)
				return FALSE;

			pdu += consumed;
			pi->pos = pdu - pi->pdu;

			if (out) {
				out->type = token;
				out->value = WSP_PARAMETER_VALUE_DATE;
				out->integer = i;
			}

			return TRUE;
		}

		case WSP_PARAMETER_TYPE_SECURE:
			if (*pdu != 0)
				return FALSE;

			pdu += 1;
			pi->pos = pdu - pi->pdu;

			if (out) {
				out->type = token;
				out->value = WSP_PARAMETER_VALUE_TEXT;
				out->text = (const char *) pdu - 1;
			}

			return TRUE;

		case WSP_PARAMETER_TYPE_CHARSET:
		{
			unsigned int i;
			const char *charset;

			if (wsp_decode_integer(pdu, end - pdu,
						&i, &consumed) == FALSE)
				return FALSE;

			charset = get_text_entry(i, charset_assignments);
			if (charset == NULL)
				return FALSE;

			pdu += consumed;
			pi->pos = pdu - pi->pdu;

			if (out) {
				out->type = token;
				out->value = WSP_PARAMETER_VALUE_TEXT;
				out->text = charset;
			}

			return TRUE;
		}

		case WSP_PARAMETER_TYPE_CONTENT_TYPE:
		{
			const char *ct;

			if (*pdu & 0x80) {
				unsigned int i = *pdu & 0x7f;

				if (i >= LAST_CONTENT_TYPE)
					return FALSE;

				ct = content_types[i];
				pdu += 1;
			} else if ((ct = wsp_decode_text(pdu, end - pdu,
							&consumed)))
				pdu += consumed;
			else
				return FALSE;

			pi->pos = pdu - pi->pdu;

			if (out) {
				out->type = token;
				out->value = WSP_PARAMETER_VALUE_TEXT;
				out->text = ct;
			}

			return TRUE;
		}

		/* TODO */
		case WSP_PARAMETER_TYPE_Q:
		default:
			return FALSE;
		}
	}

	untyped = wsp_decode_text(pdu, end - pdu, &consumed);
	if (untyped == NULL)
		return FALSE;

	pdu += consumed;

	if (wsp_decode_integer(pdu, end - pdu, &token, &consumed) == TRUE) {
		pdu += consumed;
		pi->pos = pdu - pi->pdu;

		out->type = WSP_PARAMETER_TYPE_UNTYPED;
		out->value = WSP_PARAMETER_VALUE_INT;
		out->integer = token;
		out->untyped = untyped;

		return TRUE;
	}

	value = wsp_decode_text(pdu, end - pdu, &consumed);
	if (value == NULL)
		return FALSE;

	pdu += consumed;
	pi->pos = pdu - pi->pdu;

	out->type = WSP_PARAMETER_TYPE_UNTYPED;
	out->value = WSP_PARAMETER_VALUE_TEXT;
	out->text = value;
	out->untyped = untyped;

	return TRUE;
}

static const char *decode_token(char *buf, gboolean accept_quotes,
				unsigned int *out_consumed,
				char *out_terminator)
{
	unsigned int pos = 0;
	unsigned int start;
	unsigned int end;
	char *endp;
	char terminator = '\0';

	/* Skip leading space */
	while (buf[pos] == ' ' || buf[pos] == '\t')
		pos += 1;

	if (buf[pos] == '\0')
		return NULL;

	start = pos;

	if (buf[pos] == '"') {
		if (accept_quotes == FALSE)
			return NULL;

		pos += 1;
		start = pos;

		endp = strchr(buf + pos, '"');
		if (endp == NULL)
			return NULL;

		pos = endp - buf;
		end = pos;
		pos += 1;
	} else {
		endp = strpbrk(buf + pos, sep_chars);

		if (endp == NULL)
			pos = strlen(buf);
		else
			pos = endp - buf;

		end = pos;
	}

	while (buf[pos] == ' ' || buf[pos] == '\t')
		pos += 1;

	if (buf[pos] != '\0') {
		terminator = buf[pos];
		pos += 1;
	}

	buf[end] = '\0';

	if (strpbrk(buf + start, ctl_chars) != NULL)
		return NULL;

	*out_consumed = pos;
	*out_terminator = terminator;

	return buf + start;
}

gboolean wsp_text_header_iter_init(struct wsp_text_header_iter *iter,
					const char *hdr)
{
	unsigned int len = strlen(hdr);
	char terminator;
	unsigned int consumed;
	const char *key;
	const char *value;

	if (len > MAX_TEXT_HEADER_SIZE)
		return FALSE;

	memcpy(iter->hdr, hdr, len);
	iter->hdr[len] = '\0';
	iter->pos = 0;
	iter->key = NULL;
	iter->value = NULL;

	key = decode_token(iter->hdr, FALSE, &consumed, &terminator);
	if (key == NULL)
		return FALSE;

	if (terminator != ':')
		return FALSE;

	len = consumed;

	value = decode_token(iter->hdr + len, TRUE, &consumed, &terminator);
	if (value == NULL)
		return FALSE;

	if (terminator != '\0' && terminator != ';')
		return FALSE;

	len += consumed;

	iter->key = key;
	iter->value = value;
	iter->pos = len;

	return TRUE;
}

gboolean wsp_text_header_iter_param_next(struct wsp_text_header_iter *iter)
{
	unsigned int pos = iter->pos;
	char terminator;
	unsigned int consumed;
	const char *key;
	const char *value;

	key = decode_token(iter->hdr + pos, FALSE, &consumed, &terminator);
	if (key == NULL)
		return FALSE;

	/* Empty value */
	if (terminator == ';' || terminator == '\0') {
		iter->key = key;
		iter->value = NULL;
		iter->pos += consumed;

		return TRUE;
	}

	if (terminator != '=')
		return FALSE;

	pos += consumed;

	value = decode_token(iter->hdr + pos, TRUE, &consumed, &terminator);
	if (value == NULL)
		return FALSE;

	if (terminator != '\0' && terminator != ';')
		return FALSE;

	pos += consumed;

	iter->key = key;
	iter->value = value;
	iter->pos = pos;

	return TRUE;
}

const char *wsp_text_header_iter_get_key(struct wsp_text_header_iter *iter)
{
	return iter->key;
}

const char *wsp_text_header_iter_get_value(struct wsp_text_header_iter *iter)
{
	return iter->value;
}
