# HG changeset patch # User Christopher O'Brien # Date 1118112971 0 # Node ID 8ab19bf9c3bc71916002f766574f28839b174941 # Parent 2ce8ec01a0645d00efb0373f40212993c5d8ac04 [gaim-migrate @ 12804] some mime utilities. These could probably use a little bit of work, but right now the only thing using them is the sametime plugin, and it handles the multi-part message parsing there just fine. committer: Tailor Script diff -r 2ce8ec01a064 -r 8ab19bf9c3bc src/mime.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mime.c Tue Jun 07 02:56:11 2005 +0000 @@ -0,0 +1,519 @@ + +/* + Gaim + + Gaim is the legal property of its developers, whose names are too + numerous to list here. Please refer to the COPYRIGHT file distributed + with this source distribution + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at + your option) any later version. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include + +#include +#include +#include +#include + +/* this should become "util.h" if we ever get this into gaim proper */ +#include +#include + +#include "mime.h" + + +/** @struct mime_fields + + Utility structure used in both MIME document and parts, which maps + field names to their values, and keeps an easily accessible list of + keys +*/ +struct mime_fields { + GHashTable *map; + GList *keys; +}; + + +struct _GaimMimeDocument { + struct mime_fields fields; + GList *parts; +}; + + +struct _GaimMimePart { + struct mime_fields fields; + struct _GaimMimeDocument *doc; + GString *data; +}; + + +static void fields_set(struct mime_fields *mf, + const char *key, const char *val) { + char *k, *v; + + g_assert(mf != NULL); + g_assert(mf->map != NULL); + + k = g_ascii_strdown(key, -1); + v = g_strdup(val); + + /* append to the keys list only if it's not already there */ + if(! g_hash_table_lookup(mf->map, k)) { + mf->keys = g_list_append(mf->keys, k); + } + + /* important to use insert. If the key is already in the table, then + it's already in the keys list. Insert will free the new instance + of the key rather than the old instance. */ + g_hash_table_insert(mf->map, k, v); +} + + +static const char *fields_get(struct mime_fields *mf, + const char *key) { + char *kdown; + const char *ret; + + g_assert(mf != NULL); + g_assert(mf->map != NULL); + + kdown = g_ascii_strdown(key, -1); + ret = g_hash_table_lookup(mf->map, kdown); + g_free(kdown); + + return ret; +} + + +static void fields_init(struct mime_fields *mf) { + g_assert(mf != NULL); + + mf->map = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); +} + + +static void fields_loadline(struct mime_fields *mf, + const char *line, gsize len) { + + /* split the line into key: value */ + char *key, *val; + char **tokens; + + /* feh, need it to be NUL terminated */ + key = g_strndup(line, len); + + /* split */ + val = strchr(key, ':'); + if(! val) { + g_free(key); + return; + } + *val++ = '\0'; + + /* normalize whitespace (sorta) and trim on key and value */ + tokens = g_strsplit(key, "\t\r\n", 0); + key = g_strjoinv("", tokens); + key = g_strstrip(key); + g_strfreev(tokens); + + tokens = g_strsplit(val, "\t\r\n", 0); + val = g_strjoinv("", tokens); + val = g_strstrip(val); + g_strfreev(tokens); + + fields_set(mf, key, val); + + g_free(key); + g_free(val); +} + + +static void fields_load(struct mime_fields *mf, + char **buf, gsize *len) { + char *tail; + + while( (tail = g_strstr_len(*buf, *len, "\r\n")) ) { + char *line; + gsize ln; + + /* determine the current line */ + line = *buf; + ln = tail - line; + + /* advance our search space past the CRLF */ + *buf = tail + 2; + *len -= (ln + 2); + + /* empty line, end of headers */ + if(! ln) return; + + /* look out for line continuations */ + if(line[ln-1] == ';') { + tail = g_strstr_len(*buf, *len, "\r\n"); + if(tail) { + gsize cln; + + cln = tail - *buf; + ln = tail - line; + + /* advance our search space past the CRLF (again) */ + *buf = tail + 2; + *len -= (cln + 2); + } + } + + /* process our super-cool line */ + fields_loadline(mf, line, ln); + } +} + + +static void field_write(const char *key, const char *val, GString *str) { + g_string_append_printf(str, "%s: %s\r\n", key, val); +} + + +static void fields_write(struct mime_fields *mf, GString *str) { + g_assert(mf != NULL); + + g_hash_table_foreach(mf->map, (GHFunc) field_write, str); + g_string_append(str, "\r\n"); +} + + +static void fields_destroy(struct mime_fields *mf) { + g_assert(mf != NULL); + + g_hash_table_destroy(mf->map); + g_list_free(mf->keys); + + mf->map = NULL; + mf->keys = NULL; +} + + +static GaimMimePart *part_new(GaimMimeDocument *doc) { + GaimMimePart *part; + + part = g_new0(GaimMimePart, 1); + fields_init(&part->fields); + part->doc = doc; + part->data = g_string_new(NULL); + + doc->parts = g_list_append(doc->parts, part); + + return part; +} + + +static void part_load(GaimMimePart *part, + const char *buf, gsize len) { + + char *b = (char *) buf; + gsize n = len; + + fields_load(&part->fields, &b, &n); + + /* the remainder will have a blank line, if there's anything at all, + so check if there's anything then trim off the trailing four + bytes, \r\n\r\n */ + if(n > 4) n -= 4; + g_string_append_len(part->data, b, n); +} + + +static void part_write(GaimMimePart *part, GString *str) { + fields_write(&part->fields, str); + g_string_append_printf(str, "%s\r\n\r\n", part->data->str); +} + + +static void part_free(GaimMimePart *part) { + + fields_destroy(&part->fields); + + g_string_free(part->data, TRUE); + part->data = NULL; + + g_free(part); +} + + +GaimMimePart *gaim_mime_part_new(GaimMimeDocument *doc) { + g_return_val_if_fail(doc != NULL, NULL); + return part_new(doc); +} + + +const GList *gaim_mime_part_get_fields(GaimMimePart *part) { + g_return_val_if_fail(part != NULL, NULL); + return part->fields.keys; +} + + +const char *gaim_mime_part_get_field(GaimMimePart *part, + const char *field) { + + g_return_val_if_fail(part != NULL, NULL); + return fields_get(&part->fields, field); +} + + +char *gaim_mime_part_get_field_decoded(GaimMimePart *part, + const char *field) { + + const char *f; + + g_return_val_if_fail(part != NULL, NULL); + + f = fields_get(&part->fields, field); + return gaim_mime_decode_field(f); +} + + +void gaim_mime_part_set_field(GaimMimePart *part, + const char *field, + const char *value) { + g_return_if_fail(part != NULL); + fields_set(&part->fields, field, value); +} + + +const char *gaim_mime_part_get_data(GaimMimePart *part) { + g_return_val_if_fail(part != NULL, NULL); + g_assert(part->data != NULL); + + return part->data->str; +} + + +void gaim_mime_part_get_data_decoded(GaimMimePart *part, + char **data, gsize *len) { + const char *enc; + + g_return_if_fail(part != NULL); + g_return_if_fail(data != NULL); + g_return_if_fail(len != NULL); + + g_assert(part->data != NULL); + + enc = gaim_mime_part_get_field(part, "content-transfer-encoding"); + + if(! enc) { + *data = g_strdup(part->data->str); + *len = part->data->len; + + } else if(! g_ascii_strcasecmp(enc, "7bit")) { + *data = g_strdup(part->data->str); + *len = part->data->len; + + } else if(! g_ascii_strcasecmp(enc, "base16")) { + *len = gaim_base16_decode(part->data->str, (unsigned char **) data); + + } else if(! g_ascii_strcasecmp(enc, "base64")) { + gaim_base64_decode(part->data->str, data, len); + + } else if(! g_ascii_strcasecmp(enc, "quoted-printable")) { + gaim_quotedp_decode(part->data->str, data, len); + + } else { + gaim_debug_warning("mime", "gaim_mime_part_get_data_decoded:" + " unknown encoding '%s'\n", enc); + *data = NULL; + *len = 0; + } +} + + +gsize gaim_mime_part_get_length(GaimMimePart *part) { + g_return_val_if_fail(part != NULL, 0); + g_assert(part->data != NULL); + + return part->data->len; +} + + +void gaim_mime_part_set_data(GaimMimePart *part, const char *data) { + g_return_if_fail(part != NULL); + g_string_free(part->data, TRUE); + part->data = g_string_new(data); +} + + +GaimMimeDocument *gaim_mime_document_new() { + GaimMimeDocument *doc; + + doc = g_new0(GaimMimeDocument, 1); + fields_init(&doc->fields); + + return doc; +} + + +static void doc_parts_load(GaimMimeDocument *doc, + const char *boundary, + const char *buf, gsize len) { + + char *b = (char *) buf; + gsize n = len; + + const char *bnd; + gsize bl; + + bnd = g_strdup_printf("--%s", boundary); + bl = strlen(bnd); + + for(b = g_strstr_len(b, n, bnd); b; ) { + char *tail; + + /* skip the boundary */ + b += bl; + n -= bl; + + /* skip the trailing \r\n or -- as well */ + if(n >= 2) { + b += 2; + n -= 2; + } + + /* find the next boundary */ + tail = g_strstr_len(b, n, bnd); + + if(tail) { + gsize sl; + + sl = tail - b; + if(sl) { + GaimMimePart *part = part_new(doc); + part_load(part, b, sl); + } + } + + b = tail; + } +} + + +GaimMimeDocument *gaim_mime_document_parsen(const char *buf, gsize len) { + + GaimMimeDocument *doc; + + char *b = (char *) buf; + gsize n = len; + + g_return_val_if_fail(buf != NULL, NULL); + + doc = gaim_mime_document_new(); + + if(! len) return doc; + + fields_load(&doc->fields, &b, &n); + + { + const char *ct = fields_get(&doc->fields, "content-type"); + if(ct && g_str_has_prefix(ct, "multipart")) { + char *bd = strrchr(ct, '='); + if(bd++) { + doc_parts_load(doc, bd, b, n); + } + } + } + + return doc; +} + + +GaimMimeDocument *gaim_mime_document_parse(const char *buf) { + g_return_val_if_fail(buf != NULL, NULL); + return gaim_mime_document_parsen(buf, strlen(buf)); +} + + +void gaim_mime_document_write(GaimMimeDocument *doc, GString *str) { + const char *bd = NULL; + + g_return_if_fail(doc != NULL); + g_return_if_fail(str != NULL); + + { + const char *ct = fields_get(&doc->fields, "content-type"); + if(ct && g_str_has_prefix(ct, "multipart")) { + char *b = strrchr(ct, '='); + if(b++) bd = b; + } + } + + fields_write(&doc->fields, str); + + if(bd) { + GList *l; + + for(l = doc->parts; l; l = l->next) { + g_string_append_printf(str, "--%s\r\n", bd); + + part_write(l->data, str); + + if(! l->next) { + g_string_append_printf(str, "--%s--\r\n", bd); + } + } + } +} + + +const GList *gaim_mime_document_get_fields(GaimMimeDocument *doc) { + g_return_val_if_fail(doc != NULL, NULL); + return doc->fields.keys; +} + + +const char *gaim_mime_document_get_field(GaimMimeDocument *doc, + const char *field) { + g_return_val_if_fail(doc != NULL, NULL); + return fields_get(&doc->fields, field); +} + + +void gaim_mime_document_set_field(GaimMimeDocument *doc, + const char *field, + const char *value) { + g_return_if_fail(doc != NULL); + fields_set(&doc->fields, field, value); +} + + +const GList *gaim_mime_document_get_parts(GaimMimeDocument *doc) { + g_return_val_if_fail(doc != NULL, NULL); + return doc->parts; +} + + +void gaim_mime_document_free(GaimMimeDocument *doc) { + if(! doc) return; + + fields_destroy(&doc->fields); + + while(doc->parts) { + part_free(doc->parts->data); + doc->parts = g_list_delete_link(doc->parts, doc->parts); + } + + g_free(doc); +} + diff -r 2ce8ec01a064 -r 8ab19bf9c3bc src/mime.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mime.h Tue Jun 07 02:56:11 2005 +0000 @@ -0,0 +1,199 @@ + +/* + Gaim + + Gaim is the legal property of its developers, whose names are too + numerous to list here. Please refer to the COPYRIGHT file distributed + with this source distribution + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at + your option) any later version. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#ifndef _GAIM_MIME_H +#define _GAIM_MIME_H + + +#include +#include + + +/** + @file mime.h + @ingroup core + + Rudimentary parsing of multi-part MIME messages into more + accessible structures. +*/ + + +/** @typedef GaimMimeDocument + A MIME document + */ +typedef struct _GaimMimeDocument GaimMimeDocument; + + +/** @typedef GaimMimePart + A part of a multipart MIME document + */ +typedef struct _GaimMimePart GaimMimePart; + + +/** Allocate an empty MIME document */ +GaimMimeDocument *gaim_mime_document_new(); + + +/** Frees memory used in a MIME document and all of its parts and fields + @param doc the MIME document to free + */ +void gaim_mime_document_free(GaimMimeDocument *doc); + + +/** Parse a MIME document from a NUL-terminated string + @param buf the NULL-terminated string containing the MIME-encoded data + @returns MIME document + */ +GaimMimeDocument *gaim_mime_document_parse(const char *buf); + + +/** Parse a MIME document from a string + @param buf the string containing the MIME-encoded data + @param len length of buf + @returns MIME document + */ +GaimMimeDocument *gaim_mime_document_parsen(const char *buf, gsize len); + + +/** Write (append) a MIME document onto a GString */ +void gaim_mime_document_write(GaimMimeDocument *doc, GString *str); + + +/** The list of fields in the header of a document + + @param doc the MIME document + @returns list of strings indicating the fields (but not the values of + the fields) in the header of doc +*/ +const GList *gaim_mime_document_get_fields(GaimMimeDocument *doc); + + +/** Get the value of a specific field in the header of a document + + @param doc the MIME document + @param field case-insensitive field name + @returns value associated with the indicated header field, or + NULL if the field doesn't exist +*/ +const char *gaim_mime_document_get_field(GaimMimeDocument *doc, + const char *field); + + +/** Set or replace the value of a specific field in the header of a + document + + @param doc the MIME document + @param field case-insensitive field name + @param value value to associate with the indicated header field, + of NULL to remove the field +*/ +void gaim_mime_document_set_field(GaimMimeDocument *doc, + const char *field, + const char *value); + + +/** The list of parts in a multipart document + + @param doc the MIME document + @returns list of GaimMimePart contained within doc +*/ +const GList *gaim_mime_document_get_parts(GaimMimeDocument *doc); + + +/** Create and insert a new part into a MIME document + + @param doc the new part's parent MIME document + */ +GaimMimePart *gaim_mime_part_new(GaimMimeDocument *doc); + + +/** The list of fields in the header of a document part + + @param part the MIME document part + @returns list of strings indicating the fields (but not the values + of the fields) in the header of part +*/ +const GList *gaim_mime_part_get_fields(GaimMimePart *part); + + +/** Get the value of a specific field in the header of a document part + + @param part the MIME document part + @param field case-insensitive name of the header field + @returns value of the specified header field, or NULL if the + field doesn't exist +*/ +const char *gaim_mime_part_get_field(GaimMimePart *part, + const char *field); + + +/** Get the decoded value of a specific field in the header of a + document part */ +char *gaim_mime_part_get_field_decoded(GaimMimePart *part, + const char *field); + + +/** Set or replace the value of a specific field in the header of a + document + @param doc the MIME document + @param field case-insensitive field name + @param value value to associate with the indicated header field, + of NULL to remove the field +*/ +void gaim_mime_part_set_field(GaimMimePart *part, + const char *field, + const char *value); + + +/** Get the (possibly encoded) data portion of a MIME document part + @param part the MIME document part + @returns NULL-terminated data found in the document part + */ +const char *gaim_mime_part_get_data(GaimMimePart *part); + + +/** Get the data portion of a MIME document part, after attempting to + decode it according to the content-transfer-encoding field. If the + specified encoding method is not supported, this function will + return NULL. + + @param part the MIME documemt part + @param +*/ +void gaim_mime_part_get_data_decoded(GaimMimePart *part, + char **data, gsize *len); + + +/** Get the length of the data portion of a MIME document part + + @param part the MIME document part + @returns length of the data in the document part +*/ +gsize gaim_mime_part_get_length(GaimMimePart *part); + + +void gaim_mime_part_set_data(GaimMimePart *part, const char *data); + + +#endif