/*- * $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 * Copyright (c) 2002 CodeFactory AB. All rights reserved. */ /* http://rfc.codefactory.se/rfcview.php?RFC=2045 */ #include #include #include #include "rr-mime.h" #ifdef G_PLATFORM_WIN32 #include #else /* For gethostname */ #include #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