changeset 10978:8ab19bf9c3bc

[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 <tailor@pidgin.im>
author Christopher O'Brien <siege@pidgin.im>
date Tue, 07 Jun 2005 02:56:11 +0000
parents 2ce8ec01a064
children fa71e39f7e3a
files src/mime.c src/mime.h
diffstat 2 files changed, 718 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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 <stdio.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/ghash.h>
+#include <glib/glist.h>
+#include <glib/gstring.h>
+
+/* this should become "util.h" if we ever get this into gaim proper */
+#include <debug.h>
+#include <util.h>
+
+#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);
+}
+
--- /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 <glib.h>
+#include <glib/glist.h>
+
+
+/**
+   @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