/*-
 * $Id: rr-mime.c,v 1.2 2002/12/20 11:57:11 jonas Exp $
 *
 * See the file LICENSE for redistribution information. 
 * If you have not received a copy of the license, please contact CodeFactory
 * by email at info@codefactory.se, or on the web at http://www.codefactory.se/
 * You may also write to: CodeFactory AB, SE-903 47, Umeå, Sweden.
 *
 * Copyright (c) 2002 Jonas Borgström <jonas@codefactory.se>
 * Copyright (c) 2002 CodeFactory AB.  All rights reserved.
 */
/* http://rfc.codefactory.se/rfcview.php?RFC=2045 */

#include <stdio.h>
#include <string.h>
#include <time.h>
#include "rr-mime.h"

#ifdef G_PLATFORM_WIN32
#include <winsock.h>
#else                              /* For gethostname */
#include <unistd.h>
#endif

/* #define TESTING */

#define CONTENT_TYPE "Content-Type"
#define CONTENT_ID   "Content-ID"

typedef enum {
	STATE_HEADER_BEFORE_NAME,
	STATE_HEADER_NAME,
	STATE_HEADER_BEFORE_VALUE,
	STATE_HEADER_VALUE,
	STATE_HEADER_ERROR
} header_parse_state;

/**
 * rr_mime_part_new:
 * @type: a mime content type
 * 
 * Creates a new mime part and sets the "Content-Type"
 * header to @type
 * 
 * Return value: A new mime part.
 **/
RRMimePart *
rr_mime_part_new (const gchar *type)
{
	RRMimePart *part;

	part = g_new0 (RRMimePart, 1);
	if (type) {
		rr_mime_part_set_header (part, CONTENT_TYPE, type);
	}

	return part;
}

static void
generate_multipart_header (RRMimePart *part, const gchar *type)
{
	gchar *str;

	g_return_if_fail (part != NULL);
	g_return_if_fail (type != NULL);
	part->boundary = g_new (gchar, 17);

	sprintf (part->boundary, "%08x%08x", 
		 g_random_int (), g_random_int ());
	part->boundary_len = 16;

	str = g_strdup_printf ("%s;\r\n\tboundary=\"%s\"", type, part->boundary);
	rr_mime_part_set_header (part, CONTENT_TYPE, str);
	g_free (str);
}

/**
 * rr_mime_multipart_new:
 * @type: a mime content type
 * 
 * Creates a new mime multi-part and sets the "Content-Type"
 * header to @type
 * 
 * Return value: A new mime multi-part.
 **/
RRMimePart *
rr_mime_multipart_new (const gchar *type)
{
	RRMimePart *part;

	part = 	part = g_new0 (RRMimePart, 1);
	part->multipart = TRUE;
	if (type) {
		generate_multipart_header (part, type);
	}

	return part;
}

/**
 * rr_mime_part_free:
 * @part: a mime part
 * 
 * Returns the resources allocated by the mime part to 
 * the system. Appended subparts will also recursively 
 * be freed.
 **/
void
rr_mime_part_free (RRMimePart *part)
{
	g_return_if_fail (part != NULL);

	g_slist_foreach (part->subparts, (GFunc)rr_mime_part_free, NULL);
	g_slist_free (part->subparts);
	g_hash_table_destroy (part->headers);
	g_free (part->multipart_type);
	g_free (part->boundary);
	if (part->should_free)
		g_free (part->body);

	g_free (part);
}

/**
 * rr_mime_part_set_unique_id:
 * @part: a #RRMimePart
 * 
 * Generates a world-unique Content-ID header
 **/
void
rr_mime_part_set_unique_id (RRMimePart *part)
{
	gchar buffer[10 + 16 + 1 + 255 + 1];

	sprintf (buffer, "RoadRunner%08lx%08x@", time (NULL), g_random_int ());
	/* Append a hostname */
	if (gethostname (buffer + 10 + 16 + 1, 255) < 0) {
		strcpy (buffer, "gethostname_failed");
	}
	/* make sure it is null terminated */
	buffer[sizeof(buffer) - 1] = 0;

	rr_mime_part_set_header (part, CONTENT_ID, buffer);
}

/**
 * rr_mime_part_set_header:
 * @part: a mime part
 * @name: the header name to set
 * @value: the value to use
 * 
 * Set the mime header named @name to @value.
 **/
void
rr_mime_part_set_header (RRMimePart *part, 
			 const gchar *name,
			 const gchar *value)
{
	g_return_if_fail (part  != NULL);
	g_return_if_fail (name  != NULL);
	g_return_if_fail (value != NULL);

	if (part->headers == NULL) {
		part->headers = g_hash_table_new_full (g_str_hash, 
						       g_str_equal, 
						       g_free, g_free);
	}
	g_hash_table_insert (part->headers, g_strdup (name), g_strdup (value));
}

/**
 * rr_mime_part_has_header:
 * @part: 
 * @name: 
 * 
 * Checks whether a header named @name exists or not.
 * 
 * Return value: %TRUE is the header exists else %FALSE.
 **/
gboolean
rr_mime_part_has_header (RRMimePart *part, const gchar *name)
{
	gpointer arg1, arg2;
	g_return_val_if_fail (part          != NULL, FALSE);
	g_return_val_if_fail (name          != NULL, FALSE);
	g_return_val_if_fail (part->headers != NULL, FALSE);

	return g_hash_table_lookup_extended (part->headers, name, 
					     &arg1, &arg2);
}

/**
 * rr_mime_part_get_header:
 * @part: mime part
 * @name: header name
 * 
 * retrieves the value of a mime header
 * 
 * Return value: the header value or %NULL.
 **/
const gchar *
rr_mime_part_get_header (RRMimePart *part, const gchar *name)
{
	g_return_val_if_fail (part          != NULL, NULL);
	g_return_val_if_fail (name          != NULL, NULL);
	g_return_val_if_fail (part->headers != NULL, NULL);

	return g_hash_table_lookup (part->headers, name);
}

/**
 * rr_mime_part_get_id:
 * @part: mime part
 * 
 * Return value: the header value or the "Content-ID" header.
 **/
const gchar *
rr_mime_part_get_id (RRMimePart *part)
{
	return rr_mime_part_get_header (part, CONTENT_ID);
}

/**
 * rr_mime_part_set_body:
 * @part: mime part
 * @data: mime body string
 * @len: mime body length.
 * @should_free: determines if the data should be freed when
 * the mime part is destroyed.
 * 
 * Sets the mime body to @data.
 **/
void
rr_mime_part_set_body (RRMimePart *part, 
		       gchar *data, gsize len,
		       gboolean should_free)
{
	g_return_if_fail (part != NULL);

	g_free (part->body);
	if (should_free) {
		part->body = g_new(gchar, len);
		memcpy (part->body, data, len);
	}
	else
		part->body = data;
	part->body_len = len;
	part->should_free = should_free;
}

/**
 * rr_mime_part_get_body:
 * @part: mime part
 * 
 * the mime body
 * 
 * Return value: a pointer to the mime body 
 **/
const gchar *
rr_mime_part_get_body (RRMimePart *part)
{
	g_return_val_if_fail (part != NULL, NULL);

	return part->body;
}

/**
 * rr_mime_part_get_body_len:
 * @part: mime part
 * 
 * mime body length
 * 
 * Return value: the mime body length
 **/
gsize
rr_mime_part_get_body_len (RRMimePart *part)
{
	g_return_val_if_fail (part != NULL, -1);

	return part->body_len;
}

/**
 * rr_mime_part_append:
 * @part: mime part
 * @subpart: the subpart to append.
 * 
 * Appends @subpart to @part.
 **/
void
rr_mime_part_append (RRMimePart *part, RRMimePart *subpart)
{
	g_return_if_fail (part != NULL);
	g_return_if_fail (subpart != NULL);
	g_return_if_fail (part->multipart == TRUE);

	part->subparts = g_slist_append (part->subparts, subpart);
}

static RRMimePart *
find_helper (RRMimePart *part, const gchar *header, const gchar *hvalue, 
	     RRMimePart *iter, gboolean *found)
{
	RRMimePart *ret;
	const gchar *value;
	GSList *list;

	g_return_val_if_fail (part   != NULL, NULL);
	g_return_val_if_fail (hvalue != NULL, NULL);

	if (*found) {
		value = g_hash_table_lookup (part->headers, header);
		if (value && strcmp (value, hvalue) == 0) {
			return part;
		}
	}
	if (iter == part) {
		*found = TRUE;
	}
	list = part->subparts;
	while (list) {
		ret = find_helper (list->data, header, hvalue, iter, found);
		if (ret)
			return ret;
		list = list->next;
	}
	return NULL;
}
/**
 * rr_mime_part_find:
 * @part: a mime part
 * @content_id: identification string
 * 
 * Searches (depth first) for a (sub)part with a "Content-ID" header
 * of value @content_id.
 * 
 * Return value: The first part found or %NULL.
 **/
RRMimePart *
rr_mime_part_find (RRMimePart *part, const gchar *content_id)
{
	gboolean found = TRUE;

	g_return_val_if_fail (part       != NULL, NULL);
	g_return_val_if_fail (content_id != NULL, NULL);

	return find_helper (part, CONTENT_ID, content_id, NULL, &found);
}

/**
 * rr_mime_part_find_type:
 * @part: a mime part
 * @content_type: type to search for
 * @iter: start the search after this part. or %NULL
 * 
 * Searches (depth first) for a (sub)part with a "Content-Type" header
 * of value @content_type.
 * 
 * Return value: The first part found or %NULL.
 **/
RRMimePart *
rr_mime_part_find_type (RRMimePart *part, 
			const gchar *content_type, 
			RRMimePart *iter)
{
	gboolean found = iter == NULL;

	g_return_val_if_fail (part         != NULL, NULL);
	g_return_val_if_fail (content_type != NULL, NULL);

	return find_helper (part, CONTENT_TYPE, content_type, iter, &found);
}

static RRMimePart *
get_next_helper (RRMimePart *part, RRMimePart *iter, gboolean *found)
{
	GSList *list;
	RRMimePart *ret;

	if (part == iter) {
		*found = TRUE;
		return NULL;
	}
	if (part->multipart == FALSE) {
		return *found ? part : NULL;
	}
	else {
		list = part->subparts;
		while (list) {
			ret = get_next_helper (list->data, iter, found);
			if (ret)
				return ret;
			list = list->next;
		}
	}
	return NULL;
}

/**
 * rr_mime_part_get_next:
 * @part: a mime part
 * @iter: optional iterator
 * 
 * Returns the mime part that comes after @iter when doing a depth
 * first search.
 * @iter of value %NULL will result in the first non-multipart part
 * to be returned.
 * 
 * Return value: a mime part or %NULL.
 **/
RRMimePart *
rr_mime_part_get_next (RRMimePart *part, RRMimePart *iter)
{
	gboolean found = iter == NULL;
	g_return_val_if_fail (part != NULL, NULL);

	return get_next_helper (part, iter, &found);
}

/**
 * rr_mime_part_foreach:
 * @part: mime part
 * @func: callback function
 * @user_data: callback user_data
 * 
 * iterates through and calls @func on all subparts.
 **/
void
rr_mime_part_foreach (RRMimePart *part, RRMimeFunc func, gpointer user_data)
{
	GSList *iter;
	
	g_return_if_fail (part != NULL);
	g_return_if_fail (func != NULL);

	func (part, user_data);
	
	iter = part->subparts;
	while (iter) {
		rr_mime_part_foreach (iter->data, func, user_data);
		iter = iter->next;
	}
}

static gchar *
strip_crlf (gchar *str)
{
	gchar *dst = str, *src = str;

	while (*src) {
		if (*src == '\r' && *(src + 1) == '\n')
			src += 2;
		else
			*dst++ = *src++;
	}
	*dst = 0;
	return str;
}

/* Ugly(!) but fast(?) */
static GHashTable*
parse_headers (const gchar *data, gsize len, gsize *size)
{
	GHashTable *headers;
	header_parse_state state = STATE_HEADER_BEFORE_NAME;
	const gchar *ptr = data;
	const gchar *name;
	const gchar *value;
	gsize name_len, value_len;
	gsize orig_len;
	gboolean done = FALSE;

	g_return_val_if_fail (data != NULL, NULL);
	g_return_val_if_fail (len  >= 0, NULL);

	headers = g_hash_table_new_full (g_str_hash, g_str_equal, 
					 g_free, g_free);

	orig_len = len;
	while (len && !done) {
		if (*ptr == '\r' && (len < 2 || *(ptr + 1) != '\n')) {
			state = STATE_HEADER_ERROR;
			break;
		}
		else if (*ptr == '\r') {
			ptr++;
			len--;
			continue;
		}
		switch (state) {
		case STATE_HEADER_BEFORE_NAME:
			if (*ptr == '\n' || *ptr == 0) {
				done = TRUE;
				break;
			}
			if (*ptr != ' ' && *ptr != '\t') {
				state = STATE_HEADER_NAME;
				name  = ptr;
				name_len = 1;
			}
			break;
		case STATE_HEADER_NAME:
			if (*ptr == ':') {
				state = STATE_HEADER_BEFORE_VALUE;
			}
			else
				name_len++;
			break;
		case STATE_HEADER_BEFORE_VALUE:
			if (*ptr != ' ' && *ptr != '\t') {
				state = STATE_HEADER_VALUE;
				value  = ptr;
				value_len = 1;
			}
			break;
		case STATE_HEADER_VALUE:
			if ((len == 1 || *ptr == '\n') &&
			    !((*(ptr+1) == ' ' || *(ptr+1) == '\t'))) {

				state = STATE_HEADER_BEFORE_NAME;
				g_hash_table_insert (headers,
						     g_strndup (name, 
								name_len),
						     strip_crlf (g_strndup (value, 
								value_len)));
			}
			else if (*ptr == '\n') {
				value_len+=2;
			}
			else
				value_len++;
			break;
		default:
			break;
		};
		ptr++;
		len--;
	}
	if (state != STATE_HEADER_BEFORE_NAME) {
		g_hash_table_destroy (headers);
		return NULL;
	}
	*size = orig_len - len;
	return headers;
}

static void
header_size_func (const gchar *name, const gchar *value, gsize *size)
{
	g_return_if_fail (name  != NULL);
	g_return_if_fail (value != NULL);
	g_return_if_fail (size  != NULL);

	/* name + ': ' + value + "\r\n" */
	*size += strlen (name) + 2 + strlen (value) + 2;
}

static gsize
calc_header_size (GHashTable *headers)
{
	gsize size = 0;

	g_return_val_if_fail (headers != NULL, 0);

	g_hash_table_foreach (headers, (GHFunc)header_size_func, &size);

	return size + 2;
}

/**
 * rr_mime_part_to_string_len:
 * @part: mime part
 * 
 * calculate how long the string representation of the
 * mime part will be in bytes.
 * 
 * Return value: the number of bytes required.
 **/
gsize
rr_mime_part_to_string_len (RRMimePart *part)
{
	GSList *iter;
	gsize header_size, size;

	g_return_val_if_fail (part != NULL, 0);

	header_size = calc_header_size (part->headers);
	if (!part->multipart) {
		return header_size + part->body_len;
	}
	iter = part->subparts;
	size = 2 + part->boundary_len + 2;
	while (iter) {
		size += rr_mime_part_to_string_len (iter->data);
		if (iter->next)
			size += 4 + part->boundary_len + 2;
		iter = iter->next;
	}
	size += 4 + part->boundary_len + 4;
	return header_size + size;
}

static void
header_render_func (const gchar *name, const gchar *value, gchar **ptr)
{
	g_return_if_fail (name  != NULL);
	g_return_if_fail (value != NULL);
	g_return_if_fail (ptr   != NULL);
	g_return_if_fail (*ptr  != NULL);

	while (*name)
		*(*ptr)++ = *name++;
	*(*ptr)++ = ':';
	*(*ptr)++ = ' ';
	while (*value)
		*(*ptr)++ = *value++;
	*(*ptr)++ = '\r';
	*(*ptr)++ = '\n';
}

static gsize
render_headers (GHashTable *headers, gchar *str)
{
	gchar *ptr = str;

	g_return_val_if_fail (headers != NULL, 0);
	g_return_val_if_fail (str != NULL, 0);

	g_hash_table_foreach (headers, (GHFunc)header_render_func, &ptr);
	*ptr++ = '\r';
	*ptr++ = '\n';
	*ptr = 0;

	return ptr - str;
}

/**
 * rr_mime_part_render:
 * @part: mime part
 * @str: buffer to render to
 * 
 * renders a string representation of @part to the
 * buffer @str.
 *
 * Note: The string isn't null terminated and @buffer has
 * to be at least rr_mime_part_to_string_len (@part) bytes long.
 * 
 * Return value: number of bytes written.
 **/
gsize
rr_mime_part_render (RRMimePart *part, gchar *str)
{
	gchar *ptr = str;
	GSList *iter;
	gchar *start_boundary;
	gchar *end_boundary;
	gsize start_len, end_len;

	g_return_val_if_fail (part != NULL, 0);
	g_return_val_if_fail (str  != NULL, 0);

	if (part->multipart == FALSE) {
		ptr += render_headers (part->headers, ptr);
		memcpy (ptr, part->body, part->body_len);
		ptr += part->body_len;
		*ptr = 0;
		return ptr - str;
	}

	start_boundary = g_strdup_printf ("\r\n--%s\r\n", part->boundary);
	end_boundary = g_strdup_printf ("\r\n--%s--\r\n", part->boundary);
	start_len = strlen (start_boundary);
	end_len   = strlen (end_boundary);

	ptr += render_headers (part->headers, ptr);

	iter = part->subparts;
	memcpy (ptr, start_boundary + 2, start_len - 2);
	ptr += start_len - 2;
	while (iter) {
		ptr += rr_mime_part_render (iter->data, ptr);
		if (iter->next) {
			memcpy (ptr, start_boundary, start_len);
			ptr += start_len;
		}
		iter = iter->next;
	}
	memcpy (ptr, end_boundary, end_len);
	ptr += end_len;
	g_free (start_boundary);
	g_free (end_boundary);

	return ptr - str;
}

/**
 * rr_mime_part_to_string:
 * @part: mime part
 * 
 * Allocates a new string containing the text representation
 * of the mime part. The string is null terminated.
 * 
 * Return value: a newly allocated string representation 
 * of the mime part.
 **/
gchar *
rr_mime_part_to_string (RRMimePart *part)
{
	gsize size, ret;
	gchar *str;

	g_return_val_if_fail (part != NULL, NULL);

	size = rr_mime_part_to_string_len (part);
	
	str = g_new (gchar, size + 1);
	ret = rr_mime_part_render (part, str);
	g_assert (size == ret);
	str[size] = 0;
	return str;
}

static gboolean
is_multipart (GHashTable *headers)
{
	const gchar *value;
	value = g_hash_table_lookup (headers, CONTENT_TYPE);
	return value && strstr (value, "multipart/");
}

static gboolean 
is_tspecials(gchar c)
{
	return (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
		c == ',' || c == ';' || c == ':' || c == '\\' || c == '\'' ||
		c == '/' || c == '[' || c == ']' || c == '?' || c == '=');
}

static gchar *
get_boundary (GHashTable *headers)
{
	const gchar *value;
	gchar *ptr, *start, *end;

	value = g_hash_table_lookup (headers, CONTENT_TYPE);
	if (value == NULL) {
		return NULL;
	}
	ptr = strstr (value, "boundary=");
	if (ptr == NULL) {
		return NULL;
	}
	ptr += 9;
	if (*ptr == '"') {
		start = end = ptr + 1;
		while (*end && *end != '"')
			end++;
		return g_strndup (start, end - start);
	}
	else {
		start = end = ptr;
		while (*end && !is_tspecials(*end) && *end != ' ')
			end++;
		return g_strndup (start, end - start);
	}
}

static gboolean
rr_mime_part_parse (RRMimePart *part, GHashTable *headers, 
		    const gchar *data, gsize len)
{
	gchar *start_boundary, *end_boundary;
	gboolean last = FALSE;
	const gchar *start_ptr, *end_ptr;
	gsize start, end, start_len, end_len;
	GHashTable *subheaders;
	const gchar *subdata;
	gsize hsize, subdata_len;
	RRMimePart *subpart;
	gboolean ret = FALSE;

	g_return_val_if_fail (part    != NULL, FALSE);
	g_return_val_if_fail (headers != NULL, FALSE);
	g_return_val_if_fail (data    != NULL, FALSE);
	g_return_val_if_fail (len     >= 0,    FALSE);

	part->headers     = headers;
	part->body        = (gchar *)data;
	part->should_free = FALSE;
	part->body_len    = len;

	if (!is_multipart (headers)) {
		return TRUE;
	}
	part->multipart = TRUE;
	if (!(part->boundary = get_boundary (headers))) {
		return FALSE;
	}
	part->boundary_len = strlen (part->boundary);

	start_boundary = g_strdup_printf ("--%s\r\n", part->boundary);
	end_boundary   = g_strdup_printf ("\r\n--%s", part->boundary);
	start_len = strlen (start_boundary);
	end_len = strlen (end_boundary);

	start_ptr = data - 1;
	start = start_ptr - data;
	while (!last) {
		start_ptr = g_strstr_len (start_ptr + 1, 
					  len - start - 1, 
					  start_boundary);
		if (start_ptr == NULL) {
			goto done;
		}
		if (start_ptr == NULL) {
			goto done;
		}
		start = start_ptr - data;
		end_ptr = start_ptr + 1;
		end = end_ptr - data;
		for (;;) {
			end_ptr = g_strstr_len (end_ptr + 1, 
						len - end - 1, 
						end_boundary);
			if (end_ptr == NULL) {
				goto done;
			}
			end = end_ptr - data;
			if (len - end >= end_len + 2 &&
			    strncmp (end_ptr + end_len, "\r\n", 2) == 0) {
				break;
			}
			if (len - end >= end_len + 4 &&
			    strncmp (end_ptr + end_len, "--\r\n", 4) == 0) {
				last = TRUE;
				break;
			}
		}
		subdata = start_ptr + start_len;
		subdata_len = end - start - start_len;
		subheaders = parse_headers (subdata, subdata_len, &hsize);
		if (subheaders == NULL)
			goto done;

		subdata += hsize;
		subdata_len -= hsize;
		g_assert (subdata_len >= 0);

		subpart = rr_mime_part_new (NULL);
		if (!rr_mime_part_parse (subpart, subheaders, 
					 subdata, subdata_len)) {
			rr_mime_part_free (subpart);
			goto done;
		}
		rr_mime_part_append (part, subpart);
	}
	ret = TRUE;
 done:
	g_free (start_boundary);
	g_free (end_boundary);
	return ret;
}

/**
 * rr_mime_parse:
 * @data: mime data
 * @len: data length
 * 
 * Parse a MIME message.
 * 
 * Return value: a newly created RRMimePart or %NULL.
 **/
RRMimePart *
rr_mime_parse (const gchar *data, gsize len)
{
	RRMimePart *part;
	GHashTable *headers;
	gsize hsize;

	headers = parse_headers (data, len, &hsize);
	if (headers == NULL) {
		return FALSE;
	}
	part = rr_mime_part_new (NULL);
	if (!rr_mime_part_parse (part, headers, data + hsize, len - hsize)) {
		rr_mime_part_free (part);
		return NULL;
	}
	return part;
}

#ifdef TESTING

const gchar test1[] = \
"Content-Type: multipart/related; boundary=boundary;\r\n" \
"              start=\"<1@example.com>\";\r\n"                \
"              type=\"application/beep+xml\"\r\n"             \
"\r\n"
"--boundary\r\n" \
"Content-Type: text/plain\r\n" \
"Content-ID: <1@example.com>\r\n" \
"\r\n" \
"Payload 1\r\n" \
"--boundary\r\n" \
"Content-Type: text/html\r\n" \
"Content-ID: <2@example.com>\r\n" \
"\r\n" \
"Payload 2\r\n" \
"--boundary\r\n" \
"Content-Type: multipart/related; boundary=\"boundary2\"\r\n" \
"\r\n" \
"--boundary2\r\n" \
"Content-Type: text/css\r\n" \
"Content-ID: <3@example.com>\r\n" \
"\r\n" \
"Payload 3\r\n" \
"--boundary2\r\n" \
"Content-Type: application/beep+xml\r\n" \
"Content-ID: <4@example.com>\r\n" \
"\r\n" \
"Payload 4\r\n" \
"--boundary2--\r\n" \
"\r\n" \
"--boundary--\r\n";

const gchar test2[] = \
"Content-Type: text plain\r\n" \
"\r\n" \
"foo";

const gchar test3[] = \
"\r\n" \
"Foo";

static void
test_generate ()
{
	RRMimePart *part, *parsed;
	gchar *str;

	part = rr_mime_part_new ("application/beep+xml");
	rr_mime_part_set_body (part, "foo", 3, FALSE);

	str = rr_mime_part_to_string (part);

	g_assert (strlen (str) == rr_mime_part_to_string_len (part));

	parsed = rr_mime_parse (str, strlen (str));
	g_assert (parsed != NULL);
	rr_mime_part_free (parsed);

	g_free (str);
	rr_mime_part_free (part);
}

static void
test_generate2 ()
{
	RRMimePart *multi1, *multi2, *part1, *part2, *part3, *part4;
	RRMimePart *ret;
	gchar *str;

	multi1 = rr_mime_multipart_new ("multipart/related");
	multi2 = rr_mime_multipart_new ("multipart/related");

	part1 = rr_mime_part_new ("text/plain");
	rr_mime_part_set_body (part1, "foo", 3, FALSE);
	part2 = rr_mime_part_new ("text/plain");
	rr_mime_part_set_body (part2, "foo2", 4, FALSE);

	part3 = rr_mime_part_new ("text/plain");
	rr_mime_part_set_body (part3, "foo3", 4, FALSE);
	part4 = rr_mime_part_new ("text/plain");
	rr_mime_part_set_body (part4, "foo4", 4, FALSE);

	rr_mime_part_append (multi1, part1);
	rr_mime_part_append (multi1, part2);
	rr_mime_part_append (multi1, multi2);

	rr_mime_part_append (multi2, part3);
	rr_mime_part_append (multi2, part4);

	str = rr_mime_part_to_string (multi1);

	g_assert (strlen (str) == rr_mime_part_to_string_len (multi1));

	ret = rr_mime_parse (str, strlen (str));
	g_assert (ret != NULL);
	rr_mime_part_free (ret);

	g_free (str);
	rr_mime_part_free (multi1);
}

static void
test_parse (const gchar *data, gsize len)
{
	RRMimePart *part, *part2;
	gchar *str;

	part = rr_mime_parse (data, len);
	g_assert (part != NULL);

	str = rr_mime_part_to_string (part);
	g_assert (str != NULL);

	g_assert (strlen (str) == rr_mime_part_to_string_len (part));

	part2 = rr_mime_parse (str, rr_mime_part_to_string_len (part));
	g_assert (part != NULL);

	g_free (str);
	rr_mime_part_free (part2);
	rr_mime_part_free (part);
}

static void
test_get_next ()
{
	RRMimePart *part, *iter;

	part = rr_mime_parse (test1, sizeof (test1) - 1);
	g_assert (part != NULL);

	g_assert ((iter = rr_mime_part_get_next (part, NULL)));
	g_assert ((iter = rr_mime_part_get_next (part, iter)));
	g_assert ((iter = rr_mime_part_get_next (part, iter)));
	g_assert ((iter = rr_mime_part_get_next (part, iter)));
	g_assert ((iter = rr_mime_part_get_next (part, iter)) == NULL);

	rr_mime_part_free (part);
}

static void
test_find ()
{
	RRMimePart *part;
	RRMimePart *part1, *part2, *part3, *part4;;
	

	part = rr_mime_parse (test1, sizeof (test1) - 1);
	g_assert (part != NULL);

	g_assert ((part4 = rr_mime_part_find (part, "<4@example.com>", NULL)));
	g_assert ((part3 = rr_mime_part_find (part, "<3@example.com>", NULL)));
	g_assert ((part2 = rr_mime_part_find (part, "<2@example.com>", NULL)));
	g_assert ((part1 = rr_mime_part_find (part, "<1@example.com>", NULL)));

	g_assert (rr_mime_part_find (part, "<2@example.com>", part1));
	g_assert (rr_mime_part_find (part, "<3@example.com>", part2));
	g_assert (rr_mime_part_find (part, "<4@example.com>", part3));

	g_assert (!rr_mime_part_find (part, "<1@example.com>", part2));
	g_assert (!rr_mime_part_find (part, "<2@example.com>", part3));
	g_assert (!rr_mime_part_find (part, "<3@example.com>", part4));

	rr_mime_part_free (part);
}

static void
test_find_type ()
{
	RRMimePart *part;
	RRMimePart *part1, *part2, *part3, *part4;;

	part = rr_mime_parse (test1, sizeof (test1) - 1);
	g_assert (part != NULL);

	g_assert ((part1 = rr_mime_part_find_type (part, "text/plain", NULL)));
	g_assert ((part2 = rr_mime_part_find_type (part, "text/html", NULL)));
	g_assert ((part3 = rr_mime_part_find_type (part, "text/css", NULL)));
	g_assert ((part4 = rr_mime_part_find_type (part, "application/beep+xml", NULL)));
	g_assert (rr_mime_part_find_type (part, "text/html", part1));
	g_assert (rr_mime_part_find_type (part, "text/css", part2));
	g_assert (rr_mime_part_find_type (part, "application/beep+xml", part3));

	g_assert (!rr_mime_part_find_type (part, "text/plain", part2));
	g_assert (!rr_mime_part_find_type (part, "text/html", part3));
	g_assert (!rr_mime_part_find_type (part, "text/css", part4));

	rr_mime_part_free (part);
}

int
main (int argc, char **argv)
{
	g_print ("testing mime generation(1)\n");
	test_generate();
	g_print ("testing mime generation(2)\n");
	test_generate2();
	g_print ("testing mime parsing(1)\n");
	test_parse(test1, sizeof (test1) - 1);
	g_print ("testing mime parsing(2)\n");
	test_parse(test2, sizeof (test2) - 1);
	g_print ("testing mime parsing(3)\n");
	test_parse(test3, sizeof (test3) - 1);

	g_print ("testing get_next\n");
	test_get_next ();
	g_print ("testing find\n");
	test_find ();
	g_print ("testing find_type\n");
	test_find_type ();

	return 0;
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1