/*-
* $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