/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* GMime * Copyright (C) 2000-2007 Jeffrey Stedfast * * 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "gmime-common.h" #include "gmime-object.h" #include "gmime-stream-mem.h" #include "gmime-utils.h" struct _type_bucket { char *type; GType object_type; GHashTable *subtype_hash; }; struct _subtype_bucket { char *subtype; GType object_type; }; static void g_mime_object_class_init (GMimeObjectClass *klass); static void g_mime_object_init (GMimeObject *object, GMimeObjectClass *klass); static void g_mime_object_finalize (GObject *object); static void init (GMimeObject *object); static void add_header (GMimeObject *object, const char *header, const char *value); static void set_header (GMimeObject *object, const char *header, const char *value); static const char *get_header (GMimeObject *object, const char *header); static void remove_header (GMimeObject *object, const char *header); static void set_content_type (GMimeObject *object, GMimeContentType *content_type); static char *get_headers (GMimeObject *object); static ssize_t write_to_stream (GMimeObject *object, GMimeStream *stream); static ssize_t write_content_type (GMimeStream *stream, const char *name, const char *value); static void type_registry_init (void); static GHashTable *type_hash = NULL; static GObjectClass *parent_class = NULL; GType g_mime_object_get_type (void) { static GType type = 0; if (!type) { static const GTypeInfo info = { sizeof (GMimeObjectClass), NULL, /* base_class_init */ NULL, /* base_class_finalize */ (GClassInitFunc) g_mime_object_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GMimeObject), 0, /* n_preallocs */ (GInstanceInitFunc) g_mime_object_init, }; type = g_type_register_static (G_TYPE_OBJECT, "GMimeObject", &info, 0); } return type; } static void g_mime_object_class_init (GMimeObjectClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_ref (G_TYPE_OBJECT); object_class->finalize = g_mime_object_finalize; klass->init = init; klass->add_header = add_header; klass->set_header = set_header; klass->get_header = get_header; klass->remove_header = remove_header; klass->set_content_type = set_content_type; klass->get_headers = get_headers; klass->write_to_stream = write_to_stream; type_registry_init (); } static void g_mime_object_init (GMimeObject *object, GMimeObjectClass *klass) { object->content_type = NULL; object->headers = g_mime_header_new (); object->content_id = NULL; g_mime_header_register_writer (object->headers, "Content-Type", write_content_type); } static void g_mime_object_finalize (GObject *object) { GMimeObject *mime = (GMimeObject *) object; if (mime->content_type) g_mime_content_type_destroy (mime->content_type); if (mime->headers) g_mime_header_destroy (mime->headers); g_free (mime->content_id); G_OBJECT_CLASS (parent_class)->finalize (object); } static ssize_t write_content_type (GMimeStream *stream, const char *name, const char *value) { GMimeContentType *content_type; ssize_t nwritten; GString *out; char *val; out = g_string_new (""); g_string_printf (out, "%s: ", name); content_type = g_mime_content_type_new_from_string (value); val = g_mime_content_type_to_string (content_type); g_string_append (out, val); g_free (val); g_mime_param_write_to_string (content_type->params, TRUE, out); g_mime_content_type_destroy (content_type); nwritten = g_mime_stream_write (stream, out->str, out->len); g_string_free (out, TRUE); return nwritten; } /** * g_mime_object_ref: * @object: mime object * * Ref's a MIME object. * * WARNING: This method is deprecated. Use g_object_ref() instead. **/ void g_mime_object_ref (GMimeObject *object) { g_return_if_fail (GMIME_IS_OBJECT (object)); g_object_ref (object); } /** * g_mime_object_unref: * @object: mime object * * Unref's a MIME object. * * WARNING: This method is deprecated. Use g_object_unref() instead. **/ void g_mime_object_unref (GMimeObject *object) { g_return_if_fail (GMIME_IS_OBJECT (object)); g_object_unref (object); } /** * g_mime_object_register_type: * @type: mime type * @subtype: mime subtype * @object_type: object type * * Registers the object type @object_type for use with the * g_mime_object_new_type() convenience function. * * Note: You may use the wildcard "*" to match any type and/or * subtype. **/ void g_mime_object_register_type (const char *type, const char *subtype, GType object_type) { struct _type_bucket *bucket; struct _subtype_bucket *sub; g_return_if_fail (object_type != 0); g_return_if_fail (subtype != NULL); g_return_if_fail (type != NULL); type_registry_init (); if (!(bucket = g_hash_table_lookup (type_hash, type))) { bucket = g_new (struct _type_bucket, 1); bucket->type = g_strdup (type); bucket->object_type = *type == '*' ? object_type : 0; bucket->subtype_hash = g_hash_table_new (g_mime_strcase_hash, g_mime_strcase_equal); g_hash_table_insert (type_hash, bucket->type, bucket); } sub = g_new (struct _subtype_bucket, 1); sub->subtype = g_strdup (subtype); sub->object_type = object_type; g_hash_table_insert (bucket->subtype_hash, sub->subtype, sub); } static void init (GMimeObject *object) { /* no-op */ } /** * g_mime_object_new_type: * @type: mime type * @subtype: mime subtype * * Performs a lookup of registered #GMimeObject subclasses, registered * using g_mime_object_register_type(), to find an appropriate class * capable of handling MIME parts of type @type/@subtype. If no class * has been registered to handle that type, it looks for a registered * class that can handle @type. If that also fails, then it will use * the generic part class, #GMimePart. * * Returns an appropriate #GMimeObject registered to handle mime-types * of @type/@subtype. **/ GMimeObject * g_mime_object_new_type (const char *type, const char *subtype) { struct _type_bucket *bucket; struct _subtype_bucket *sub; GMimeObject *object; GType obj_type; g_return_val_if_fail (type != NULL, NULL); type_registry_init (); if ((bucket = g_hash_table_lookup (type_hash, type))) { if (!(sub = g_hash_table_lookup (bucket->subtype_hash, subtype))) sub = g_hash_table_lookup (bucket->subtype_hash, "*"); obj_type = sub ? sub->object_type : 0; } else { bucket = g_hash_table_lookup (type_hash, "*"); obj_type = bucket ? bucket->object_type : 0; } if (!obj_type) { /* use the default mime object */ if ((bucket = g_hash_table_lookup (type_hash, "*"))) { sub = g_hash_table_lookup (bucket->subtype_hash, "*"); obj_type = sub ? sub->object_type : 0; } if (!obj_type) return NULL; } object = g_object_new (obj_type, NULL); GMIME_OBJECT_GET_CLASS (object)->init (object); return object; } static void sync_content_type (GMimeObject *object) { GMimeContentType *content_type; GMimeParam *params; GString *string; char *type, *p; content_type = object->content_type; string = g_string_new ("Content-Type: "); type = g_mime_content_type_to_string (content_type); g_string_append (string, type); g_free (type); if ((params = content_type->params)) g_mime_param_write_to_string (params, FALSE, string); p = string->str; g_string_free (string, FALSE); type = p + strlen ("Content-Type: "); g_mime_header_set (object->headers, "Content-Type", type); g_free (p); } static void set_content_type (GMimeObject *object, GMimeContentType *content_type) { if (object->content_type) g_mime_content_type_destroy (object->content_type); object->content_type = content_type; sync_content_type (object); } /** * g_mime_object_set_content_type: * @object: MIME object * @mime_type: MIME type * * Sets the content-type for the specified MIME object. **/ void g_mime_object_set_content_type (GMimeObject *object, GMimeContentType *mime_type) { g_return_if_fail (GMIME_IS_OBJECT (object)); g_return_if_fail (mime_type != NULL); GMIME_OBJECT_GET_CLASS (object)->set_content_type (object, mime_type); } /** * g_mime_object_get_content_type: * @object: MIME object * * Gets the Content-Type object for the given MIME object or %NULL on * fail. * * Returns the content-type object for the specified MIME object. **/ const GMimeContentType * g_mime_object_get_content_type (GMimeObject *object) { g_return_val_if_fail (GMIME_IS_OBJECT (object), NULL); return object->content_type; } /** * g_mime_object_set_content_type_parameter: * @object: MIME object * @name: param name * @value: param value * * Sets the content-type param @name to the value @value. **/ void g_mime_object_set_content_type_parameter (GMimeObject *object, const char *name, const char *value) { g_return_if_fail (GMIME_IS_OBJECT (object)); g_return_if_fail (name != NULL); g_mime_content_type_set_parameter (object->content_type, name, value); sync_content_type (object); } /** * g_mime_object_get_content_type_parameter: * @object: MIME object * @name: param name * * Gets the value of the content-type param @name set on the MIME part * @object. * * Returns the value of the requested content-type param or %NULL on * if the param doesn't exist. **/ const char * g_mime_object_get_content_type_parameter (GMimeObject *object, const char *name) { g_return_val_if_fail (GMIME_IS_OBJECT (object), NULL); g_return_val_if_fail (name != NULL, NULL); return g_mime_content_type_get_parameter (object->content_type, name); } /** * g_mime_object_set_content_id: * @object: MIME object * @content_id: content-id (addr-spec portion) * * Sets the Content-Id of the MIME object. **/ void g_mime_object_set_content_id (GMimeObject *object, const char *content_id) { char *msgid; g_return_if_fail (GMIME_IS_OBJECT (object)); g_free (object->content_id); object->content_id = g_strdup (content_id); msgid = g_strdup_printf ("<%s>", content_id); g_mime_object_set_header (object, "Content-Id", msgid); g_free (msgid); } /** * g_mime_object_get_content_id: * @object: MIME object * * Gets the Content-Id of the MIME object or NULL if one is not set. * * Returns a const pointer to the Content-Id header. **/ const char * g_mime_object_get_content_id (GMimeObject *object) { g_return_val_if_fail (GMIME_IS_OBJECT (object), NULL); return object->content_id; } enum { HEADER_CONTENT_TYPE, HEADER_CONTENT_ID, HEADER_UNKNOWN, }; static char *headers[] = { "Content-Type", "Content-Id", NULL }; static gboolean process_header (GMimeObject *object, const char *header, const char *value) { GMimeContentType *content_type; int i; for (i = 0; headers[i]; i++) { if (!g_ascii_strcasecmp (headers[i], header)) break; } switch (i) { case HEADER_CONTENT_TYPE: content_type = g_mime_content_type_new_from_string (value); g_mime_object_set_content_type (object, content_type); break; case HEADER_CONTENT_ID: g_free (object->content_id); object->content_id = g_mime_utils_decode_message_id (value); break; default: return FALSE; } g_mime_header_set (object->headers, header, value); return TRUE; } static void add_header (GMimeObject *object, const char *header, const char *value) { if (!process_header (object, header, value)) g_mime_header_add (object->headers, header, value); } /** * g_mime_object_add_header: * @object: mime object * @header: header name * @value: header value * * Adds an arbitrary header to the MIME object. **/ void g_mime_object_add_header (GMimeObject *object, const char *header, const char *value) { g_return_if_fail (GMIME_IS_OBJECT (object)); g_return_if_fail (header != NULL); g_return_if_fail (value != NULL); GMIME_OBJECT_GET_CLASS (object)->add_header (object, header, value); } static void set_header (GMimeObject *object, const char *header, const char *value) { if (!process_header (object, header, value)) g_mime_header_set (object->headers, header, value); } /** * g_mime_object_set_header: * @object: mime object * @header: header name * @value: header value * * Sets an arbitrary header on the MIME object. **/ void g_mime_object_set_header (GMimeObject *object, const char *header, const char *value) { g_return_if_fail (GMIME_IS_OBJECT (object)); g_return_if_fail (header != NULL); g_return_if_fail (value != NULL); GMIME_OBJECT_GET_CLASS (object)->set_header (object, header, value); } static const char * get_header (GMimeObject *object, const char *header) { return g_mime_header_get (object->headers, header); } /** * g_mime_object_get_header: * @object: mime object * @header: header name * * Gets the value of the requested header if it exists or %NULL * otherwise. * * Returns the value of the header @header if it exists or %NULL * otherwise. **/ const char * g_mime_object_get_header (GMimeObject *object, const char *header) { g_return_val_if_fail (GMIME_IS_OBJECT (object), NULL); g_return_val_if_fail (header != NULL, NULL); return GMIME_OBJECT_GET_CLASS (object)->get_header (object, header); } static void remove_header (GMimeObject *object, const char *header) { g_mime_header_remove (object->headers, header); } /** * g_mime_object_remove_header: * @object: mime object * @header: header name * * Removed the specified header if it exists. **/ void g_mime_object_remove_header (GMimeObject *object, const char *header) { g_return_if_fail (GMIME_IS_OBJECT (object)); g_return_if_fail (header != NULL); GMIME_OBJECT_GET_CLASS (object)->remove_header (object, header); } static char * get_headers (GMimeObject *object) { return g_mime_header_to_string (object->headers); } /** * g_mime_object_get_headers: * @object: mime object * * Allocates a string buffer containing all of the MIME object's raw * headers. * * Returns an allocated string containing all of the raw MIME headers. **/ char * g_mime_object_get_headers (GMimeObject *object) { g_return_val_if_fail (GMIME_IS_OBJECT (object), NULL); return GMIME_OBJECT_GET_CLASS (object)->get_headers (object); } static ssize_t write_to_stream (GMimeObject *object, GMimeStream *stream) { return -1; } /** * g_mime_object_write_to_stream: * @object: mime object * @stream: stream * * Write the contents of the MIME object to @stream. * * Returns -1 on fail. **/ ssize_t g_mime_object_write_to_stream (GMimeObject *object, GMimeStream *stream) { g_return_val_if_fail (GMIME_IS_OBJECT (object), -1); g_return_val_if_fail (GMIME_IS_STREAM (stream), -1); return GMIME_OBJECT_GET_CLASS (object)->write_to_stream (object, stream); } /** * g_mime_object_to_string: * @object: mime object * * Allocates a string buffer containing the contents of @object. * * Returns an allocated string containing the contents of the mime * object. **/ char * g_mime_object_to_string (GMimeObject *object) { GMimeStream *stream; GByteArray *array; char *str; g_return_val_if_fail (GMIME_IS_OBJECT (object), NULL); array = g_byte_array_new (); stream = g_mime_stream_mem_new (); g_mime_stream_mem_set_byte_array (GMIME_STREAM_MEM (stream), array); g_mime_object_write_to_stream (object, stream); g_object_unref (stream); g_byte_array_append (array, (unsigned char *) "", 1); str = (char *) array->data; g_byte_array_free (array, FALSE); return str; } static void subtype_bucket_foreach (gpointer key, gpointer value, gpointer user_data) { struct _subtype_bucket *bucket = value; g_free (bucket->subtype); g_free (bucket); } static void type_bucket_foreach (gpointer key, gpointer value, gpointer user_data) { struct _type_bucket *bucket = value; g_free (bucket->type); if (bucket->subtype_hash) { g_hash_table_foreach (bucket->subtype_hash, subtype_bucket_foreach, NULL); g_hash_table_destroy (bucket->subtype_hash); } g_free (bucket); } static void type_registry_shutdown (void) { g_hash_table_foreach (type_hash, type_bucket_foreach, NULL); g_hash_table_destroy (type_hash); } static void type_registry_init (void) { if (type_hash) return; type_hash = g_hash_table_new (g_mime_strcase_hash, g_mime_strcase_equal); g_atexit (type_registry_shutdown); }