diff options
author | NotZed <NotZed@HelixCode.com> | 2000-04-21 07:48:45 +0800 |
---|---|---|
committer | Michael Zucci <zucchi@src.gnome.org> | 2000-04-21 07:48:45 +0800 |
commit | 383f245d39b8b806a4b6ce4dea7b0ab7f1658450 (patch) | |
tree | b6447bbcd6bd8c4b31582a343ce062e8d2c548c4 | |
parent | 6b5b1f6de926a4c9dec95dbd55ae53a5b30fb6a5 (diff) | |
download | gsoc2013-evolution-383f245d39b8b806a4b6ce4dea7b0ab7f1658450.tar gsoc2013-evolution-383f245d39b8b806a4b6ce4dea7b0ab7f1658450.tar.gz gsoc2013-evolution-383f245d39b8b806a4b6ce4dea7b0ab7f1658450.tar.bz2 gsoc2013-evolution-383f245d39b8b806a4b6ce4dea7b0ab7f1658450.tar.lz gsoc2013-evolution-383f245d39b8b806a4b6ce4dea7b0ab7f1658450.tar.xz gsoc2013-evolution-383f245d39b8b806a4b6ce4dea7b0ab7f1658450.tar.zst gsoc2013-evolution-383f245d39b8b806a4b6ce4dea7b0ab7f1658450.zip |
MERGE NEW_PARSER branch into HEAD, fixed conflicts.
2000-04-20 NotZed <NotZed@HelixCode.com>
* MERGE NEW_PARSER branch into HEAD, fixed conflicts.
* gmime-content-field.c (_print_parameter): Duh, removed again
(@@#$@ cvs merge).
* camel-mime-utils.c (header_content_type_is): Constify.
(header_content_type_unref): Killed a couple warnings.
* camel-folder.c (_init): Removed more log crap.
* providers/Makefile.am (SUBDIRS): Removed nntp, pending fixes for
summary changes.
* providers/mbox/camel-mbox-folder.c (_get_message_by_number):
Fixed for new summary interface. Added a warning for using this
broken api.
(_get_message_by_uid): Fixed for message new with session
vanishing.
svn path=/trunk/; revision=2531
42 files changed, 7068 insertions, 2664 deletions
diff --git a/camel/ChangeLog b/camel/ChangeLog index e373337fe4..1afbf83876 100644 --- a/camel/ChangeLog +++ b/camel/ChangeLog @@ -1,3 +1,24 @@ +2000-04-20 NotZed <NotZed@HelixCode.com> + + * MERGE NEW_PARSER branch into HEAD, fixed conflicts. + + * gmime-content-field.c (_print_parameter): Duh, removed again + (@@#$@ cvs merge). + + * camel-mime-utils.c (header_content_type_is): Constify. + (header_content_type_unref): Killed a couple warnings. + + * camel-folder.c (_init): Removed more log crap. + + * providers/Makefile.am (SUBDIRS): Removed nntp, pending fixes for + summary changes. + + * providers/mbox/camel-mbox-folder.c (_get_message_by_number): + Fixed for new summary interface. Added a warning for using this + broken api. + (_get_message_by_uid): Fixed for message new with session + vanishing. + 2000-04-19 Dan Winship <danw@helixcode.com> * camel-simple-data-wrapper-stream.c @@ -141,6 +162,277 @@ * providers/Makefile.am: Readded smtp now that smtp builds without error. +2000-04-20 NotZed <NotZed@HelixCode.com> + + * providers/mbox/camel-mbox-summary.c + (camel_mbox_summary_next_uid): Public function to get the next + uid, makes sure its saved to disk too. + + * camel-mime-part.c (my_finalize): Fix disposition crap with a + real disposition. + (my_set_disposition): Likewise. + (my_get_disposition): And here. + (my_write_to_stream): And here, needs more cleanup. + + * providers/mbox/camel-mbox-folder.c (_append_message): Assign a + new uid at this point. + + * gmime-content-field.c (gmime_content_field_write_to_stream): + Make something up if we have an invalid/missing content type + (i.e. text/plain). + +2000-04-19 NotZed <NotZed@HelixCode.com> + + * providers/mbox/camel-mbox-folder.c (_delete): Fixed completely + broken switch() syntax, only compiled because errno is a macro on + some systems. + (_list_subfolders): Likewise. + +2000-04-18 NotZed <NotZed@HelixCode.com> + + * camel-mime-parser.c (folder_scan_init): init stream to null. + + * providers/mbox/camel-mbox-summary.c + (CAMEL_MBOX_SUMMARY_VERSION): Moved to .c file, incremented. + (index_folder): Changed to have index passed via the summary. + (decode_string): Do a sanity check on the string size, so we dont + visit g_malloc()'s friendly abort(). + + * camel-folder-pt-proxy.c (camel_folder_pt_proxy_class_init): + Removed reference to set_name. + (_set_name): Removed. + + * providers/mbox/camel-mbox-utils.c + (parsed_information_to_mbox_summary): Removed. Most of this file + is about to be binned. + + * providers/mbox/camel-mbox-search.c (func_header_contains): Fixes + for changes to summary interface. + (struct _searchcontext): Remove pointer to message info, get it + straight from the mboxsummary. + (camel_mbox_folder_search_by_expression): New summary interface. + (camel_mbox_folder_search_by_expression): Uh, the summary is not + an object anymore (well not yet). + + * providers/mbox/camel-mbox-folder.c + (camel_mbox_folder_class_init): Removed set_name init. + (_set_name): Removed. + (_open): Call new summary interface. + (_close): Use new summary interface. + (_create): Removed a summary object leak. + (_get_message_count): New summary interface. + (_get_uid_list): Use new summary interface. FIXME: this is leaky. + (_get_message_by_uid): Use the new summary interface, some + cleanup. + (_append_message): Totally changed, basically just appends the + message directly, ignores the summary (for now), the summary will + fix itself up if it needs to. + (_check_get_or_maybe_generate_summary_file): Bye bye old code. + (summary_get_message_info): Implement get_message_info again, for + folder. + + * camel-folder.c (camel_folder_class_init): Removed set_name + setup. + (_set_name): Moved contents into _init. + (_init): Perform the old functions of set_name here. + + * camel-folder.h: Removed the set_name internal interface. + +2000-04-14 NotZed <NotZed@HelixCode.com> + + * providers/mbox/camel-mbox-summary.[ch]: Completely replaced with + new code. + + * Makefile.am (libcamel_la_SOURCES): Removed + camel-folder-summary.[ch]. + + * camel-folder.h (struct _CamelFolder): Removed summary. + (struct _CamelFolder): Changed flags to be 1 bit bitfields. + + * camel-folder-summary.[ch]: Class removed entirely. + + * camel-folder.c (camel_folder_get_summary): Removed. + (camel_folder_summary_get_message_info): Moved from + camel-folder-summary.c + (camel_folder_summary_get_subfolder_info): Moved from + camel-folder-summary.c + + * camel-mime-parser.c (folder_scan_step): Store the start of + headers and start of from in the scan state. + (camel_mime_parser_tell_start_headers): Query the start of the + headers. + (camel_mime_parser_tell_start_from): Query the cached start of + from marker. + +2000-04-13 NotZed <NotZed@HelixCode.com> + + * gmime-content-field.c (gmime_content_field_free): Removed this + function. If its too dangerous to use, it shouldn't be here. + (gmime_content_field_ref): Also ref the embedded content-type. + (gmime_content_field_unref): Ditto to unref it. + + * camel-mime-utils.h: Add a refcount for content-type header. + + * camel-mime-utils.c (header_content_type_unref): Implement unref + for content-type. + (header_content_type_ref): Implement ref for header content type. + +2000-04-12 NotZed <NotZed@HelixCode.com> + + * gmime-content-field.h: Changed to use a _header_content_type. + Added type/subtype back for compatability with clients. + + * gmime-content-field.c: Basically a total rewrite, and now just a + thin wrapper ontop of header_content_type. + (_free_parameter): Got rid of it. + (gmime_content_field_new): Use header_content_type_* functions. + (gmime_content_field_set_parameter): Likewise. + (_print_parameter): Blow away. + (gmime_content_field_write_to_stream): Get details from the + content_type field. Should check if it needs to escape chars in + the paramter value. + (gmime_content_field_get_mime_type): Likewise. + (___debug_print_parameter): Get rid of this rather annoyingly + named function. + (gmime_content_field_get_parameter): Simplified function. + (gmime_content_field_construct_from_string): Fixed this to use a + real parser. + (gmime_content_field_is_type): New function to test if a type matches. + (gmime_content_field_construct_from_string): Track type/subtype + from subordinate content_type header struct. + + * gmime-rfc2047.[ch]: Removed. Unused. + + * camel-stream-b64.[ch]: Blown away more duplicated code. + + * Makefile.am: Removed camel-stream-b64.[ch], and + gmime-base64.[ch]. + + * camel-mime-part.c (my_get_content_object): Replaced + camel-stream-b64 with camel-stream-filter/camel-mime-filter-basic. + (my_write_content_to_stream): Replaced camel-stream-b64 with the + camel-stream-filter with an encoder. + (my_get_content_object): Also implement quoted-printable decoding. + (my_write_content_to_stream): Also implement quoted-printable + encoding. + (my_get_output_stream): Took out stream-b64 code (nothing's being + executed yet anyway). + + * gmime-base64.[ch]: Blown away. Not used, dont need it. + + * camel-mime-utils.h: Added offset for this header. Records where + it is in the source. + + * camel-mime-utils.c (header_raw_append_parse): Add offset + parameter, to store where the header is stored in the stream. + (header_raw_append): Added offset param. + (header_raw_find): Return offset, if a pointer supplied for it. + (header_raw_replace): Add offset param. + (header_content_type_new): New function, to create an empty + content type. + (header_content_type_set_param): Set a parameter in the + content-type. + (header_set_param): Generic header parameter setting function. + (header_decode_string): Handle NULL input. + + * camel-mime-parser.c (camel_mime_parser_headers_raw): New + function to get access to all the raw headers. + (folder_scan_header): Keep track of the header start position, and + store it when saving the header. + +2000-04-11 NotZed <NotZed@HelixCode.com> + + * camel-mime-utils.c: Moved a bunch of printf's to debug. + + * camel-mime-parser.c: Moved a bunch of printf's to debug. + (folder_scan_header): Detect end of each header line using the + last scanned char, and not the last scanned position. + + * camel-mime-filter-index.[ch]: Indexing filter. Indexes unicode + sequences into ibex files. + +2000-04-09 NotZed <NotZed@HelixCode.com> + + * camel-mime-part.c: Dont include gmime-base64.h + + * camel-mime-filter-charset.c (complete): Implement the completion + function. + + * camel-mime-parser.c (folder_scan_step): If we get to the end of + the body data, check any filters for outstanding completion data. + (camel_mime_parser_scan_from): Set whether we scan for "From " + headers or not. + + * camel-stream-filter.c (do_read): If we get to end of stream on + the source, then call the filtering completion function to see if + we have any more data to return. + + * camel-mime-filter-basic.c (filter): Implement quoted printable + encoding and decoding filters. + (complete): And the complete function as well. + + * camel-mime-utils.c (base64_encode_close): Also take an input + buffer, allow closing of filters. + (quoted_encode_step): First cut, simple quoted-printable encoder. + Doesn't handle trailing spaces/tabs on end of line properly yet. + (quoted_encode_close): Complete a quoted-encoding. + (is_qpsafe): New type check, for quoted-printable safe characters + (that do not need encoding). Thats all bits used in the type + table! Rebuilt the types table. + (header_content_type_is): Checks a content type against at + type/subtype match. + (header_content_type_param): Handle NULL content type pointer. + +2000-04-08 NotZed <NotZed@HelixCode.com> + + * camel-mime-filter-basic.c (filter): Implement the base64 + encoder. Problem is, there is no way to know when to close it. + Close/Reset will have to provide the same args as filter, so it can + flush remaining data *sigh* + + * camel-mime-utils.c (base64_encode_step): A rather complex base64 + encoder, fast? + (base64_step_close): Companion function to finish off the base64 + sequence. + + * camel-mime-part.c (my_write_content_to_stream): Changed to use + camel_stream_write_to_stream(). + + * camel-stream.[ch] (camel_stream_write_to_stream): From + camel_stream_b64_write_to_stream(). Fixed some infinite loop + bugs with error conditions. + + * camel-stream-b64.[ch] (camel_stream_b64_write_to_stream): Removed. + This has nothing to do with stream-b64, so i've moved it to + CamelStream. + + * camel-mime-utils.h: Add a comment about refcounting + header_content_type struct. + + * Makefile.am: Added camel-stream-filter*.[ch]. + + * camel-stream-filter.[ch]: Class to implement a generic + (multipass) filter ontop of a stream. Only implements a read-only + stream. + + * camel-mime-parser.c (camel_mime_parser_filter_add): Ref the + filter we just added. + + * Makefile.am: Added camel-mime-filter*.[ch]. + + * camel-mime-filter-charset.[ch]: A filter to preform character set + conversion (uses unicode_iconv). + + * camel-mime-filter-save.[ch]: A simple filter which will save all + data directly to a file or file descriptor. + + * camel-mime-filter-basic.[ch]: Implements the basic mime filters, + base64 and quoted-printable decoding (encoding not implemented yet). + + * camel-mime-filter.[ch]: A filtering class, which can filter streams + of data without having to copy them. Simpler than stream classes, + and can be plugged into a single stream class (when i write it). + 2000-04-07 Dan Winship <danw@helixcode.com> * providers/pop3/camel-pop3-store.c (pop3_connect): Clarify error @@ -151,6 +443,15 @@ 2000-04-07 NotZed <NotZed@HelixCode.com> + * Makefile.am: Added camel-mime-parser/camel-mime-utils. + + * camel-mime-parser.c: Fast mime parser. + + * camel-mime-utils.c: Mime utility functions, and email header + parsers. + +2000-04-07 NotZed <NotZed@HelixCode.com> + * providers/Makefile.am: Removed smtp for now, its a long way from building. * providers/smtp/Makefile.in: Removed file that shouldn't have been diff --git a/camel/Makefile.am b/camel/Makefile.am index fbc98cea86..5e8ea1e2d4 100644 --- a/camel/Makefile.am +++ b/camel/Makefile.am @@ -39,7 +39,6 @@ libcamel_la_SOURCES = \ camel-simple-data-wrapper-stream.c \ camel-stream-data-wrapper.c \ camel-folder.c \ - camel-folder-summary.c \ camel-folder-utils.c \ camel-medium.c \ camel-marshal-utils.c \ @@ -58,20 +57,26 @@ libcamel_la_SOURCES = \ camel-session.c \ camel-store.c \ camel-stream.c \ - camel-stream-b64.c \ camel-stream-buffer.c \ camel-stream-fs.c \ camel-stream-mem.c \ camel-transport.c \ camel-url.c \ data-wrapper-repository.c \ - gmime-base64.c \ gmime-content-field.c \ gmime-utils.c \ gstring-util.c \ hash-table-utils.c \ md5-utils.c \ string-utils.c \ + camel-mime-parser.c \ + camel-mime-utils.c \ + camel-mime-filter.c \ + camel-mime-filter-basic.c \ + camel-mime-filter-save.c \ + camel-mime-filter-charset.c \ + camel-mime-filter-index.c \ + camel-stream-filter.c \ $(pthread_SRC) libcamelinclude_HEADERS = \ @@ -82,7 +87,6 @@ libcamelinclude_HEADERS = \ camel-simple-data-wrapper-stream.h \ camel-stream-data-wrapper.h \ camel-folder.h \ - camel-folder-summary.h \ camel-folder-utils.h \ camel-mime-body-part.h \ camel-marshal-utils.h \ @@ -101,14 +105,12 @@ libcamelinclude_HEADERS = \ camel-session.h \ camel-store.h \ camel-stream.h \ - camel-stream-b64.h \ camel-stream-buffer.h \ camel-stream-fs.h \ camel-stream-mem.h \ camel-transport.h \ camel-url.h \ data-wrapper-repository.h \ - gmime-base64.h \ gmime-content-field.h \ gmime-utils.h \ gstring-util.h \ @@ -116,6 +118,14 @@ libcamelinclude_HEADERS = \ md5-utils.h \ string-utils.h \ camel-exception-list.def \ + camel-mime-parser.h \ + camel-mime-utils.h \ + camel-mime-filter.h \ + camel-mime-filter-basic.h \ + camel-mime-filter-save.h \ + camel-mime-filter-charset.h \ + camel-mime-filter-index.h \ + camel-stream-filter.h \ $(pthread_HDR) libcamel_extra_sources = \ diff --git a/camel/camel-folder-pt-proxy.c b/camel/camel-folder-pt-proxy.c index cbf382c6ed..8d8fda4ccf 100644 --- a/camel/camel-folder-pt-proxy.c +++ b/camel/camel-folder-pt-proxy.c @@ -120,7 +120,6 @@ camel_folder_pt_proxy_class_init (CamelFolderPtProxyClass *camel_folder_pt_proxy camel_folder_class->open_async = _open_async; camel_folder_class->close_async = _close_async; #endif - camel_folder_class->set_name = _set_name; camel_folder_class->get_name = _get_name; camel_folder_class->can_hold_folders = _can_hold_folders; camel_folder_class->can_hold_messages = _can_hold_messages; @@ -475,25 +474,6 @@ static void _close (CamelFolder *folder, } - - - -/* folder->set_name implementation */ - -static void -_set_name (CamelFolder *folder, - const gchar *name, - CamelException *ex) -{ - CamelFolderPtProxy *proxy_folder; - - proxy_folder = CAMEL_FOLDER_PT_PROXY (folder); - CF_CLASS (proxy_folder->real_folder)-> - set_name (proxy_folder->real_folder, name, ex); - -} - - /* folder->get_name implementation */ /* this one is not executed in a thread */ static const gchar * diff --git a/camel/camel-folder-summary.h b/camel/camel-folder-summary.h deleted file mode 100644 index 720e09118f..0000000000 --- a/camel/camel-folder-summary.h +++ /dev/null @@ -1,120 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* camelFolderSummary.h : Abstract class for a folder summary */ - -/* - * - * Author : - * Bertrand Guiheneuf <bertrand@helixcode.com> - * - * Copyright 1999, 2000 Helix Code, Inc. (http://www.helixcode.com) - * - * 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 CAMEL_FOLDER_SUMMARY_H -#define CAMEL_FOLDER_SUMMARY_H 1 - - -#ifdef __cplusplus -extern "C" { -#pragma } -#endif /* __cplusplus }*/ - -#include <gtk/gtk.h> -#include "camel-types.h" - - -#define CAMEL_FOLDER_SUMMARY_TYPE (camel_folder_summary_get_type ()) -#define CAMEL_FOLDER_SUMMARY(obj) (GTK_CHECK_CAST((obj), CAMEL_FOLDER_SUMMARY_TYPE, CamelFolderSummary)) -#define CAMEL_FOLDER_SUMMARY_CLASS(k) (GTK_CHECK_CLASS_CAST ((k), CAMEL_FOLDER_SUMMARY_TYPE, CamelFolderSummaryClass)) -#define CAMEL_IS_FOLDER_SUMMARY(o) (GTK_CHECK_TYPE((o), CAMEL_FOLDER_SUMMARY_TYPE)) - - - - -typedef struct { - gchar *name; - gint nb_message; - gint nb_unread_message; - gint nb_deleted_message; -} CamelFolderInfo; - - - - -typedef struct { - - /* all the followings are public fields */ - gchar *subject; - - gchar *sender; - gchar *to; - - gchar *sent_date; - gchar *received_date; - - guint32 size; - gchar *uid; -} CamelMessageInfo; - - - - -struct _CamelFolderSummary { - GtkObject parent_object; - -}; - - - - -typedef struct { - GtkObjectClass parent_class; - - /* Virtual methods */ - int (*count_messages) (CamelFolderSummary *summary); - int (*count_subfolders) (CamelFolderSummary *summary); - - GPtrArray * (*get_subfolder_info) (CamelFolderSummary *summary, - int first, int count); - GPtrArray * (*get_message_info) (CamelFolderSummary *summary, - int first, int count); - -} CamelFolderSummaryClass; - - - -/* Standard Gtk function */ -GtkType camel_folder_summary_get_type (void); - - -/* public methods */ -int camel_folder_summary_count_messages (CamelFolderSummary *summary); -int camel_folder_summary_count_subfolders (CamelFolderSummary *summary); - -GPtrArray *camel_folder_summary_get_subfolder_info (CamelFolderSummary *summary, - int first, int count); -GPtrArray *camel_folder_summary_get_message_info (CamelFolderSummary *summary, - int first, int count); - - - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* CAMEL_FOLDER_SUMMARY_H */ diff --git a/camel/camel-folder.c b/camel/camel-folder.c index 58a2985399..31141da5c9 100644 --- a/camel/camel-folder.c +++ b/camel/camel-folder.c @@ -66,9 +66,6 @@ static void _close_async (CamelFolder *folder, #endif -static void _set_name (CamelFolder *folder, - const gchar *name, - CamelException *ex); static const gchar *_get_name (CamelFolder *folder); static const gchar *_get_full_name (CamelFolder *folder); @@ -134,7 +131,8 @@ static void _delete_message_by_uid (CamelFolder *folder, const gchar *uid, CamelException *ex); - +static GPtrArray *get_message_info (CamelFolder *folder, int first, int count); +static GPtrArray *get_subfolder_info (CamelFolder *folder, int first, int count); static void @@ -154,7 +152,6 @@ camel_folder_class_init (CamelFolderClass *camel_folder_class) #ifdef FOLDER_ASYNC_TEST camel_folder_class->close_async = _close_async; #endif - camel_folder_class->set_name = _set_name; camel_folder_class->get_name = _get_name; camel_folder_class->get_full_name = _get_full_name; camel_folder_class->can_hold_folders = _can_hold_folders; @@ -182,6 +179,9 @@ camel_folder_class_init (CamelFolderClass *camel_folder_class) camel_folder_class->delete_message_by_uid = _delete_message_by_uid; camel_folder_class->get_uid_list = _get_uid_list; + camel_folder_class->get_subfolder_info = get_subfolder_info; + camel_folder_class->get_message_info = get_message_info; + /* virtual method overload */ gtk_object_class->finalize = _finalize; } @@ -251,6 +251,9 @@ _init (CamelFolder *folder, CamelStore *parent_store, CamelFolder *parent_folder, const gchar *name, gchar separator, CamelException *ex) { + gchar *full_name; + const gchar *parent_full_name; + g_assert (folder != NULL); g_assert (parent_store != NULL); g_assert (folder->parent_store == NULL); @@ -265,7 +268,28 @@ _init (CamelFolder *folder, CamelStore *parent_store, folder->open_mode = FOLDER_OPEN_UNKNOWN; folder->open_state = FOLDER_CLOSE; folder->separator = separator; - CF_CLASS (folder)->set_name (folder, name, ex); + + /* if the folder already has a name, free it */ + g_free (folder->name); + g_free (folder->full_name); + + /* set those fields to NULL now, so that if an + exception occurs, they will be set anyway */ + folder->name = NULL; + folder->full_name = NULL; + + if (folder->parent_folder) { + parent_full_name = + camel_folder_get_full_name (folder->parent_folder); + + full_name = g_strdup_printf ("%s%c%s", parent_full_name, + folder->separator, name); + } else { + full_name = g_strdup_printf ("%c%s", folder->separator, name); + } + + folder->name = g_strdup (name); + folder->full_name = full_name; } @@ -431,44 +455,6 @@ camel_folder_close_async (CamelFolder *folder, #endif -static void -_set_name (CamelFolder *folder, - const gchar *name, - CamelException *ex) -{ - gchar *full_name; - const gchar *parent_full_name; - - g_assert (folder->parent_store != NULL); - g_assert (name != NULL); - g_assert (!camel_folder_is_open (folder)); - - /* if the folder already has a name, free it */ - g_free (folder->name); - g_free (folder->full_name); - - /* set those fields to NULL now, so that if an - exception occurs, they will be set anyway */ - folder->name = NULL; - folder->full_name = NULL; - - if (folder->parent_folder) { - parent_full_name = - camel_folder_get_full_name (folder->parent_folder); - - full_name = g_strdup_printf ("%s%c%s", parent_full_name, - folder->separator, name); - } else { - full_name = g_strdup_printf ("%c%s", folder->separator, name); - } - - folder->name = g_strdup (name); - folder->full_name = full_name; - -} - - - static const gchar * _get_name (CamelFolder *folder) { @@ -1204,42 +1190,72 @@ camel_folder_copy_message_to (CamelFolder *folder, CF_CLASS (folder)->copy_message_to (folder, message, dest_folder, ex);; } - - - - /* summary stuff */ +static GPtrArray * +get_subfolder_info (CamelFolder *folder, int first, int count) +{ + g_warning ("CamelFolder::get_subfolder_info not implemented for `%s'", gtk_type_name (GTK_OBJECT_TYPE (folder))); + return NULL; +} -gboolean -camel_folder_has_summary_capability (CamelFolder *folder) +/** + * camel_folder_summary_get_subfolder_info: return an array of subfolders + * @summary: a summary + * @first: the index of the first subfolder to return information for + * (starting from 0) + * @count: the number of subfolders to return information for + * + * Returns an array of pointers to CamelFolderInfo objects. The caller + * must free the array when it is done with it, but should not modify + * the elements. + * + * Return value: an array containing information about the subfolders. + **/ +GPtrArray * +camel_folder_summary_get_subfolder_info (CamelFolder *folder, + int first, int count) { g_assert (folder != NULL); - return folder->has_summary_capability; + return CF_CLASS (folder)->get_subfolder_info (folder, first, count); } +static GPtrArray * +get_message_info (CamelFolder *folder, int first, int count) +{ + g_warning ("CamelFolder::get_message_info not implemented for `%s'", gtk_type_name (GTK_OBJECT_TYPE (folder))); + return NULL; +} + /** - * camel_folder_get_summary: return the summary of a folder - * @folder: folder object - * @ex: exception object - * - * Return a CamelFolderSummary object from - * which the main informations about a folder - * can be retrieved. - * - * Return value: the folder summary object. + * camel_folder_summary_get_message_info: return an array of messages + * @folder: a camel folder + * @first: the index of the first message to return information for + * (starting from 0) + * @count: the number of messages to return information for + * + * Returns an array of pointers to CamelMessageInfo objects. The caller + * must free the array when it is done with it, but should not modify + * the elements. + * + * Return value: an array containing information about the messages. **/ -CamelFolderSummary * -camel_folder_get_summary (CamelFolder *folder, - CamelException *ex) +GPtrArray * +camel_folder_summary_get_message_info (CamelFolder *folder, + int first, int count) { g_assert (folder != NULL); - g_assert (camel_folder_is_open (folder)); - - return folder->summary; + return CF_CLASS (folder)->get_message_info (folder, first, count); } - +/* summary stuff */ +/* TODO: is this function required anyway? */ +gboolean +camel_folder_has_summary_capability (CamelFolder *folder) +{ + g_assert (folder != NULL); + return folder->has_summary_capability; +} /* UIDs stuff */ diff --git a/camel/camel-folder.h b/camel/camel-folder.h index 9a717e3db6..b8b2db0da5 100644 --- a/camel/camel-folder.h +++ b/camel/camel-folder.h @@ -35,6 +35,7 @@ extern "C" { #endif /* __cplusplus }*/ #include <gtk/gtk.h> +#include <time.h> #include "camel-types.h" #define CAMEL_FOLDER_TYPE (camel_folder_get_type ()) @@ -58,12 +59,56 @@ typedef enum { typedef void (*CamelFolderAsyncCallback) (); typedef void (CamelSearchFunc)(CamelFolder *folder, int id, gboolean complete, GList *matches, void *data); +/* these structs from camel-folder-summary.h ... (remove comment after cleanup soon) */ +/* TODO: perhaps they should be full-block objects? */ +/* FIXME: rename this to something more suitable */ +typedef struct { + gchar *name; + gint nb_message; /* ick, these should be renamed to something better */ + gint nb_unread_message; + gint nb_deleted_message; +} CamelFolderInfo; + +/* A tree of message content info structures + describe the content structure of the message (if it has any) */ +typedef struct _CamelMessageContentInfo { + struct _CamelMessageContentInfo *next; + + struct _CamelMessageContentInfo *childs; + struct _CamelMessageContentInfo *parent; + + struct _header_content_type *type; + char *id; + char *description; + char *encoding; + + guint32 size; +} CamelMessageContentInfo; + +/* TODO: rename this?? */ +/* TODO: Make this an object, maybe? */ +typedef struct { + /* public fields */ + gchar *subject; + gchar *to; + gchar *from; + + gchar *uid; + guint32 flags; + + + time_t date_sent; + time_t date_received; + + /* tree of content description */ + CamelMessageContentInfo *content; +} CamelMessageInfo; + + struct _CamelFolder { GtkObject parent_object; - gboolean can_hold_folders; - gboolean can_hold_messages; CamelFolderOpenMode open_mode; CamelFolderState open_state; gchar *name; @@ -73,12 +118,11 @@ struct _CamelFolder CamelFolder *parent_folder; GList *permanent_flags; - gboolean has_summary_capability; - CamelFolderSummary *summary; - - gboolean has_uid_capability; - - gboolean has_search_capability; + gboolean can_hold_folders:1; + gboolean can_hold_messages:1; + gboolean has_summary_capability:1; + gboolean has_uid_capability:1; + gboolean has_search_capability:1; }; @@ -111,10 +155,6 @@ typedef struct { gpointer user_data, CamelException *ex); - void (*set_name) (CamelFolder *folder, - const gchar *name, - CamelException *ex); - const gchar * (*get_name) (CamelFolder *folder); const gchar * (*get_full_name) (CamelFolder *folder); @@ -206,6 +246,10 @@ typedef struct { gboolean (*search_complete)(CamelFolder *folder, int searchid, gboolean wait, CamelException *ex); void (*search_cancel) (CamelFolder *folder, int searchid, CamelException *ex); + /* moved the old summary stuff from camel-folder-summary.h here */ + GPtrArray * (*get_subfolder_info) (CamelFolder *, int first, int count); + GPtrArray * (*get_message_info) (CamelFolder *, int first, int count); + } CamelFolderClass; @@ -316,6 +360,13 @@ int camel_folder_search_by_expression(CamelFolder *folder, const char *expr gboolean camel_folder_search_complete(CamelFolder *folder, int searchid, gboolean wait, CamelException *ex); void camel_folder_search_cancel(CamelFolder *folder, int searchid, CamelException *ex); +/* summary info, from the old camel-folder-summary + FIXME: rename these slightly? */ +GPtrArray *camel_folder_summary_get_subfolder_info (CamelFolder *summary, + int first, int count); +GPtrArray *camel_folder_summary_get_message_info (CamelFolder *summary, + int first, int count); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/camel/camel-mime-filter-basic.c b/camel/camel-mime-filter-basic.c new file mode 100644 index 0000000000..60566abf4e --- /dev/null +++ b/camel/camel-mime-filter-basic.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "camel-mime-filter-basic.h" + +#include "camel-mime-utils.h" + +static void camel_mime_filter_basic_class_init (CamelMimeFilterBasicClass *klass); +static void camel_mime_filter_basic_init (CamelMimeFilterBasic *obj); + +static CamelMimeFilterClass *camel_mime_filter_basic_parent; + +enum SIGNALS { + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +guint +camel_mime_filter_basic_get_type (void) +{ + static guint type = 0; + + if (!type) { + GtkTypeInfo type_info = { + "CamelMimeFilterBasic", + sizeof (CamelMimeFilterBasic), + sizeof (CamelMimeFilterBasicClass), + (GtkClassInitFunc) camel_mime_filter_basic_class_init, + (GtkObjectInitFunc) camel_mime_filter_basic_init, + (GtkArgSetFunc) NULL, + (GtkArgGetFunc) NULL + }; + + type = gtk_type_unique (camel_mime_filter_get_type (), &type_info); + } + + return type; +} + +static void +finalise(GtkObject *o) +{ + ((GtkObjectClass *)camel_mime_filter_basic_parent)->finalize (o); +} + +/* should this 'flush' outstanding state/data bytes? */ +static void +reset(CamelMimeFilter *mf) +{ + CamelMimeFilterBasic *f = (CamelMimeFilterBasic *)mf; + + f->state = 0; + f->save = 0; +} + +static void +complete(CamelMimeFilter *mf, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace) +{ + CamelMimeFilterBasic *f = (CamelMimeFilterBasic *)mf; + int newlen; + + switch(f->type) { + case CAMEL_MIME_FILTER_BASIC_BASE64_ENC: + /* wont go to more than 2x size (overly conservative) */ + camel_mime_filter_set_size(mf, len*2, FALSE); + newlen = base64_encode_close(in, len, mf->outbuf, &f->state, &f->save); + break; + case CAMEL_MIME_FILTER_BASIC_QP_ENC: + /* FIXME: *3 is probably not quite enough ... */ + camel_mime_filter_set_size(mf, len*3, FALSE); + newlen = quoted_encode_close(in, len, mf->outbuf, &f->state, &f->save); + break; + case CAMEL_MIME_FILTER_BASIC_BASE64_DEC: + /* output can't possibly exceed the input size */ + camel_mime_filter_set_size(mf, len, FALSE); + newlen = base64_decode_step(in, len, mf->outbuf, &f->state, &f->save); + break; + case CAMEL_MIME_FILTER_BASIC_QP_DEC: + /* output can't possibly exceed the input size */ + camel_mime_filter_set_size(mf, len, FALSE); + newlen = quoted_decode_step(in, len, mf->outbuf, &f->state, &f->save); + break; + default: + g_warning("unknown type %d in CamelMimeFilterBasic", f->type); + goto donothing; + } + + *out = mf->outbuf; + *outlen = newlen; + *outprespace = mf->outpre; + + return; +donothing: + *out = in; + *outlen = len; + *outprespace = prespace; +} + +/* here we do all of the basic mime filtering */ +static void +filter(CamelMimeFilter *mf, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace) +{ + CamelMimeFilterBasic *f = (CamelMimeFilterBasic *)mf; + int newlen; + + switch(f->type) { + case CAMEL_MIME_FILTER_BASIC_BASE64_ENC: + /* wont go to more than 2x size (overly conservative) */ + camel_mime_filter_set_size(mf, len*2, FALSE); + newlen = base64_encode_step(in, len, mf->outbuf, &f->state, &f->save); + break; + case CAMEL_MIME_FILTER_BASIC_QP_ENC: + /* FIXME: *3 is probably not quite enough ... */ + camel_mime_filter_set_size(mf, len*3, FALSE); + newlen = quoted_encode_step(in, len, mf->outbuf, &f->state, &f->save); + break; + case CAMEL_MIME_FILTER_BASIC_BASE64_DEC: + /* output can't possibly exceed the input size */ + camel_mime_filter_set_size(mf, len, FALSE); + newlen = base64_decode_step(in, len, mf->outbuf, &f->state, &f->save); + break; + case CAMEL_MIME_FILTER_BASIC_QP_DEC: + /* output can't possibly exceed the input size */ + camel_mime_filter_set_size(mf, len, FALSE); + newlen = quoted_decode_step(in, len, mf->outbuf, &f->state, &f->save); + break; + default: + g_warning("unknown type %d in CamelMimeFilterBasic", f->type); + goto donothing; + } + + *out = mf->outbuf; + *outlen = newlen; + *outprespace = mf->outpre; + + return; +donothing: + *out = in; + *outlen = len; + *outprespace = prespace; +} + +static void +camel_mime_filter_basic_class_init (CamelMimeFilterBasicClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + CamelMimeFilterClass *filter_class = (CamelMimeFilterClass *) klass; + + camel_mime_filter_basic_parent = gtk_type_class (camel_mime_filter_get_type ()); + + object_class->finalize = finalise; + + filter_class->reset = reset; + filter_class->filter = filter; + filter_class->complete = complete; + + gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL); +} + +static void +camel_mime_filter_basic_init (CamelMimeFilterBasic *obj) +{ + obj->state = 0; + obj->save = 0; +} + +/** + * camel_mime_filter_basic_new: + * + * Create a new CamelMimeFilterBasic object. + * + * Return value: A new CamelMimeFilterBasic widget. + **/ +CamelMimeFilterBasic * +camel_mime_filter_basic_new (void) +{ + CamelMimeFilterBasic *new = CAMEL_MIME_FILTER_BASIC ( gtk_type_new (camel_mime_filter_basic_get_type ())); + return new; +} + +CamelMimeFilterBasic * +camel_mime_filter_basic_new_type(CamelMimeFilterBasicType type) +{ + CamelMimeFilterBasic *new; + + switch (type) { + case CAMEL_MIME_FILTER_BASIC_BASE64_ENC: + case CAMEL_MIME_FILTER_BASIC_QP_ENC: + case CAMEL_MIME_FILTER_BASIC_BASE64_DEC: + case CAMEL_MIME_FILTER_BASIC_QP_DEC: + new = camel_mime_filter_basic_new(); + new->type = type; + break; + default: + g_warning("Invalid type of CamelMimeFilterBasic requested: %d", type); + new = NULL; + break; + } + return new; +} + diff --git a/camel/camel-mime-filter-basic.h b/camel/camel-mime-filter-basic.h new file mode 100644 index 0000000000..57d9898fa6 --- /dev/null +++ b/camel/camel-mime-filter-basic.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _CAMEL_MIME_FILTER_BASIC_H +#define _CAMEL_MIME_FILTER_BASIC_H + +#include <gtk/gtk.h> +#include <camel/camel-mime-filter.h> + +#define CAMEL_MIME_FILTER_BASIC(obj) GTK_CHECK_CAST (obj, camel_mime_filter_basic_get_type (), CamelMimeFilterBasic) +#define CAMEL_MIME_FILTER_BASIC_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, camel_mime_filter_basic_get_type (), CamelMimeFilterBasicClass) +#define IS_CAMEL_MIME_FILTER_BASIC(obj) GTK_CHECK_TYPE (obj, camel_mime_filter_basic_get_type ()) + +typedef struct _CamelMimeFilterBasic CamelMimeFilterBasic; +typedef struct _CamelMimeFilterBasicClass CamelMimeFilterBasicClass; + +typedef enum { + CAMEL_MIME_FILTER_BASIC_BASE64_ENC = 1, + CAMEL_MIME_FILTER_BASIC_BASE64_DEC, + CAMEL_MIME_FILTER_BASIC_QP_ENC, + CAMEL_MIME_FILTER_BASIC_QP_DEC, +} CamelMimeFilterBasicType; + +struct _CamelMimeFilterBasic { + CamelMimeFilter parent; + + struct _CamelMimeFilterBasicPrivate *priv; + + CamelMimeFilterBasicType type; + + int state; + int save; +}; + +struct _CamelMimeFilterBasicClass { + CamelMimeFilterClass parent_class; +}; + +guint camel_mime_filter_basic_get_type (void); +CamelMimeFilterBasic *camel_mime_filter_basic_new (void); +CamelMimeFilterBasic *camel_mime_filter_basic_new_type (CamelMimeFilterBasicType type); + +#endif /* ! _CAMEL_MIME_FILTER_BASIC_H */ diff --git a/camel/camel-mime-filter-charset.c b/camel/camel-mime-filter-charset.c new file mode 100644 index 0000000000..eb9db60802 --- /dev/null +++ b/camel/camel-mime-filter-charset.c @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include <unicode.h> + +#include <string.h> +#include <errno.h> + +#include "camel-mime-filter-charset.h" + + +static void camel_mime_filter_charset_class_init (CamelMimeFilterCharsetClass *klass); +static void camel_mime_filter_charset_init (CamelMimeFilterCharset *obj); + +static CamelMimeFilterClass *camel_mime_filter_charset_parent; + +enum SIGNALS { + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +guint +camel_mime_filter_charset_get_type (void) +{ + static guint type = 0; + + if (!type) { + GtkTypeInfo type_info = { + "CamelMimeFilterCharset", + sizeof (CamelMimeFilterCharset), + sizeof (CamelMimeFilterCharsetClass), + (GtkClassInitFunc) camel_mime_filter_charset_class_init, + (GtkObjectInitFunc) camel_mime_filter_charset_init, + (GtkArgSetFunc) NULL, + (GtkArgGetFunc) NULL + }; + + type = gtk_type_unique (camel_mime_filter_get_type (), &type_info); + } + + return type; +} + +static void +finalise(GtkObject *o) +{ + CamelMimeFilterCharset *f = (CamelMimeFilterCharset *)o; + + g_free(f->from); + g_free(f->to); + if (f->ic != (unicode_iconv_t)-1) { + unicode_iconv_close(f->ic); + f->ic = (unicode_iconv_t) -1; + } + + ((GtkObjectClass *)camel_mime_filter_charset_parent)->finalize (o); +} + +static void +reset(CamelMimeFilter *mf) +{ + CamelMimeFilterCharset *f = (CamelMimeFilterCharset *)mf; + char buf[16]; + char *buffer; + int outlen = 16; + + /* what happens with the output bytes if this resets the state? */ + if (f->ic != (unicode_iconv_t) -1) { + buffer = buf; + unicode_iconv(f->ic, NULL, 0, &buffer, &outlen); + } +} + +static void +complete(CamelMimeFilter *mf, char *in, size_t len, size_t prespace, char **out, size_t *outlenptr, size_t *outprespace) +{ + CamelMimeFilterCharset *f = (CamelMimeFilterCharset *)mf; + int converted; + char *inbuf, *outbuf; + int inlen, outlen; + + if (f->ic == (unicode_iconv_t) -1) { + goto donothing; + } + + /* FIXME: there's probably a safer way to size this ...? */ + /* We could always resize if we run out of room in outbuf (but it'd be nice not + to have to) */ + camel_mime_filter_set_size(mf, len*5, FALSE); + inbuf = in; + inlen = len; + outbuf = mf->outbuf; + outlen = mf->outsize; + if (inlen>0) { + converted = unicode_iconv(f->ic, &inbuf, &inlen, &outbuf, &outlen); + if (converted == -1) { + if (errno != EINVAL) { + g_warning("error occured converting: %s", strerror(errno)); + goto donothing; + } + } + + if (inlen>0) { + g_warning("Output lost in character conversion, invalid sequence encountered?"); + } + } + + /* this 'resets' the output stream, returning back to the initial + shift state for multishift charactersets */ + converted = unicode_iconv(f->ic, NULL, 0, &outbuf, &outlen); + if (converted == -1) { + g_warning("Conversion failed to complete: %s", strerror(errno)); + } + + *out = mf->outbuf; + *outlenptr = mf->outsize - outlen; + *outprespace = mf->outpre; + return; + +donothing: + *out = in; + *outlenptr = len; + *outprespace = prespace; +} + +static void +filter(CamelMimeFilter *mf, char *in, size_t len, size_t prespace, char **out, size_t *outlenptr, size_t *outprespace) +{ + CamelMimeFilterCharset *f = (CamelMimeFilterCharset *)mf; + int converted; + char *inbuf, *outbuf; + int inlen, outlen; + + if (f->ic == (unicode_iconv_t) -1) { + goto donothing; + } + + /* FIXME: there's probably a safer way to size this ...? */ + camel_mime_filter_set_size(mf, len*5, FALSE); + inbuf = in; + inlen = len; + outbuf = mf->outbuf; + outlen = mf->outsize; + converted = unicode_iconv(f->ic, &inbuf, &inlen, &outbuf, &outlen); + if (converted == -1) { + if (errno != EINVAL) { + g_warning("error occured converting: %s", strerror(errno)); + goto donothing; + } + } + + /* + NOTE: This assumes EINVAL only occurs because we ran out of + bytes for a multibyte sequence, if not, we're in trouble. + */ + + if (inlen>0) { + camel_mime_filter_backup(mf, inbuf, inlen); + } + + *out = mf->outbuf; + *outlenptr = mf->outsize - outlen; + *outprespace = mf->outpre; + return; + +donothing: + *out = in; + *outlenptr = len; + *outprespace = prespace; +} + +static void +camel_mime_filter_charset_class_init (CamelMimeFilterCharsetClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + CamelMimeFilterClass *filter_class = (CamelMimeFilterClass *) klass; + + camel_mime_filter_charset_parent = gtk_type_class (camel_mime_filter_get_type ()); + + object_class->finalize = finalise; + + filter_class->reset = reset; + filter_class->filter = filter; + filter_class->complete = complete; + + gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL); +} + +static void +camel_mime_filter_charset_init (CamelMimeFilterCharset *obj) +{ + obj->ic = (unicode_iconv_t)-1; +} + +/** + * camel_mime_filter_charset_new: + * + * Create a new CamelMimeFilterCharset object. + * + * Return value: A new CamelMimeFilterCharset widget. + **/ +CamelMimeFilterCharset * +camel_mime_filter_charset_new (void) +{ + CamelMimeFilterCharset *new = CAMEL_MIME_FILTER_CHARSET ( gtk_type_new (camel_mime_filter_charset_get_type ())); + return new; +} + +CamelMimeFilterCharset * +camel_mime_filter_charset_new_convert(const char *from_charset, const char *to_charset) +{ + CamelMimeFilterCharset *new = CAMEL_MIME_FILTER_CHARSET ( gtk_type_new (camel_mime_filter_charset_get_type ())); + + new->ic = unicode_iconv_open(to_charset, from_charset); + if (new->ic == (unicode_iconv_t) -1) { + g_warning("Cannot create charset conversion from %s to %s: %s", from_charset, to_charset, strerror(errno)); + gtk_object_unref((GtkObject *)new); + new = NULL; + } else { + new->from = g_strdup(from_charset); + new->to = g_strdup(to_charset); + } + return new; +} diff --git a/camel/camel-mime-filter-charset.h b/camel/camel-mime-filter-charset.h new file mode 100644 index 0000000000..3e15e2e7c7 --- /dev/null +++ b/camel/camel-mime-filter-charset.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _CAMEL_MIME_FILTER_CHARSET_H +#define _CAMEL_MIME_FILTER_CHARSET_H + +#include <gtk/gtk.h> +#include <camel/camel-mime-filter.h> +#include <unicode.h> + +#define CAMEL_MIME_FILTER_CHARSET(obj) GTK_CHECK_CAST (obj, camel_mime_filter_charset_get_type (), CamelMimeFilterCharset) +#define CAMEL_MIME_FILTER_CHARSET_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, camel_mime_filter_charset_get_type (), CamelMimeFilterCharsetClass) +#define IS_CAMEL_MIME_FILTER_CHARSET(obj) GTK_CHECK_TYPE (obj, camel_mime_filter_charset_get_type ()) + +typedef struct _CamelMimeFilterCharset CamelMimeFilterCharset; +typedef struct _CamelMimeFilterCharsetClass CamelMimeFilterCharsetClass; + +struct _CamelMimeFilterCharset { + CamelMimeFilter parent; + + struct _CamelMimeFilterCharsetPrivate *priv; + + unicode_iconv_t ic; + char *from; + char *to; +}; + +struct _CamelMimeFilterCharsetClass { + CamelMimeFilterClass parent_class; +}; + +guint camel_mime_filter_charset_get_type (void); +CamelMimeFilterCharset *camel_mime_filter_charset_new (void); + +CamelMimeFilterCharset *camel_mime_filter_charset_new_convert (const char *from_charset, const char *to_charset); + +#endif /* ! _CAMEL_MIME_FILTER_CHARSET_H */ diff --git a/camel/camel-mime-filter-index.c b/camel/camel-mime-filter-index.c new file mode 100644 index 0000000000..24c223d4e5 --- /dev/null +++ b/camel/camel-mime-filter-index.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "camel-mime-filter-index.h" + + +static void camel_mime_filter_index_class_init (CamelMimeFilterIndexClass *klass); +static void camel_mime_filter_index_init (CamelMimeFilterIndex *obj); + +static CamelMimeFilterClass *camel_mime_filter_index_parent; + +enum SIGNALS { + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +guint +camel_mime_filter_index_get_type (void) +{ + static guint type = 0; + + if (!type) { + GtkTypeInfo type_info = { + "CamelMimeFilterIndex", + sizeof (CamelMimeFilterIndex), + sizeof (CamelMimeFilterIndexClass), + (GtkClassInitFunc) camel_mime_filter_index_class_init, + (GtkObjectInitFunc) camel_mime_filter_index_init, + (GtkArgSetFunc) NULL, + (GtkArgGetFunc) NULL + }; + + type = gtk_type_unique (camel_mime_filter_get_type (), &type_info); + } + + return type; +} + +static void +finalise(GtkObject *o) +{ + CamelMimeFilterIndex *f = (CamelMimeFilterIndex *)o; + + g_free(f->name); + f->index = NULL; /* ibex's need refcounting? */ + + ((GtkObjectClass *)camel_mime_filter_index_parent)->finalize (o); +} + +static void +complete(CamelMimeFilter *mf, char *in, size_t len, size_t prespace, char **out, size_t *outlenptr, size_t *outprespace) +{ + CamelMimeFilterIndex *f = (CamelMimeFilterIndex *)mf; + + if (f->index == NULL || f->name==NULL) { + goto donothing; + } + + ibex_index_buffer(f->index, f->name, in, len, NULL); + +donothing: + *out = in; + *outlenptr = len; + *outprespace = prespace; +} + +static void +filter(CamelMimeFilter *mf, char *in, size_t len, size_t prespace, char **out, size_t *outlenptr, size_t *outprespace) +{ + CamelMimeFilterIndex *f = (CamelMimeFilterIndex *)mf; + int inleft = 0; + + if (f->index == NULL || f->name==NULL) { + goto donothing; + } + + ibex_index_buffer(f->index, f->name, in, len, &inleft); + + if (inleft>0) { + camel_mime_filter_backup(mf, in+(len-inleft), inleft); + } + + *out = in; + *outlenptr = len-inleft; + *outprespace = prespace; + return; + +donothing: + *out = in; + *outlenptr = len; + *outprespace = prespace; +} + +static void +camel_mime_filter_index_class_init (CamelMimeFilterIndexClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + CamelMimeFilterClass *filter_class = (CamelMimeFilterClass *) klass; + + camel_mime_filter_index_parent = gtk_type_class (camel_mime_filter_get_type ()); + + object_class->finalize = finalise; + + /*filter_class->reset = reset;*/ + filter_class->filter = filter; + filter_class->complete = complete; + + gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL); +} + +static void +camel_mime_filter_index_init (CamelMimeFilterIndex *obj) +{ +} + +/** + * camel_mime_filter_index_new: + * + * Create a new CamelMimeFilterIndex object. + * + * Return value: A new CamelMimeFilterIndex widget. + **/ +CamelMimeFilterIndex * +camel_mime_filter_index_new (void) +{ + CamelMimeFilterIndex *new = CAMEL_MIME_FILTER_INDEX ( gtk_type_new (camel_mime_filter_index_get_type ())); + return new; +} + +CamelMimeFilterIndex *camel_mime_filter_index_new_ibex (ibex *index) +{ + CamelMimeFilterIndex *new = camel_mime_filter_index_new(); + + if (new) { + new->index = index; + new->name = g_strdup(""); + } + return new; +} + +/* Set the match name for any indexed words */ +void camel_mime_filter_index_set_name (CamelMimeFilterIndex *mf, char *name) +{ + g_free(mf->name); + mf->name = g_strdup(name); +} + + diff --git a/camel/camel-mime-filter-index.h b/camel/camel-mime-filter-index.h new file mode 100644 index 0000000000..b4ea1966f0 --- /dev/null +++ b/camel/camel-mime-filter-index.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _CAMEL_MIME_FILTER_INDEX_H +#define _CAMEL_MIME_FILTER_INDEX_H + +#include <gtk/gtk.h> +#include <libibex/ibex.h> + +#include <camel/camel-mime-filter.h> + +#define CAMEL_MIME_FILTER_INDEX(obj) GTK_CHECK_CAST (obj, camel_mime_filter_index_get_type (), CamelMimeFilterIndex) +#define CAMEL_MIME_FILTER_INDEX_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, camel_mime_filter_index_get_type (), CamelMimeFilterIndexClass) +#define IS_CAMEL_MIME_FILTER_INDEX(obj) GTK_CHECK_TYPE (obj, camel_mime_filter_index_get_type ()) + +typedef struct _CamelMimeFilterIndex CamelMimeFilterIndex; +typedef struct _CamelMimeFilterIndexClass CamelMimeFilterIndexClass; + +struct _CamelMimeFilterIndex { + CamelMimeFilter parent; + + struct _CamelMimeFilterIndexPrivate *priv; + + ibex *index; + char *name; +}; + +struct _CamelMimeFilterIndexClass { + CamelMimeFilterClass parent_class; +}; + +guint camel_mime_filter_index_get_type (void); +CamelMimeFilterIndex *camel_mime_filter_index_new (void); + +CamelMimeFilterIndex *camel_mime_filter_index_new_ibex (ibex *); + +/* Set the match name for any indexed words */ +void camel_mime_filter_index_set_name (CamelMimeFilterIndex *, char *); + +#endif /* ! _CAMEL_MIME_FILTER_INDEX_H */ diff --git a/camel/camel-mime-filter-save.c b/camel/camel-mime-filter-save.c new file mode 100644 index 0000000000..b5b54a2ee6 --- /dev/null +++ b/camel/camel-mime-filter-save.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <string.h> +#include <errno.h> + +#include "camel-mime-filter-save.h" + +static void camel_mime_filter_save_class_init (CamelMimeFilterSaveClass *klass); +static void camel_mime_filter_save_init (CamelMimeFilterSave *obj); + +static CamelMimeFilterClass *camel_mime_filter_save_parent; + +enum SIGNALS { + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +guint +camel_mime_filter_save_get_type (void) +{ + static guint type = 0; + + if (!type) { + GtkTypeInfo type_info = { + "CamelMimeFilterSave", + sizeof (CamelMimeFilterSave), + sizeof (CamelMimeFilterSaveClass), + (GtkClassInitFunc) camel_mime_filter_save_class_init, + (GtkObjectInitFunc) camel_mime_filter_save_init, + (GtkArgSetFunc) NULL, + (GtkArgGetFunc) NULL + }; + + type = gtk_type_unique (camel_mime_filter_get_type (), &type_info); + } + + return type; +} + +static void +finalise(GtkObject *o) +{ + CamelMimeFilterSave *f = (CamelMimeFilterSave *)o; + + g_free(f->filename); + if (f->fd != -1) { + /* FIXME: what do we do with failed writes???? */ + close(f->fd); + } + + ((GtkObjectClass *)camel_mime_filter_save_parent)->finalize (o); +} + +static void +reset(CamelMimeFilter *mf) +{ + CamelMimeFilterSave *f = (CamelMimeFilterSave *)mf; + + /* i dunno, how do you 'reset' a file? reopen it? do i care? */ + if (f->fd != -1){ + lseek(f->fd, 0, SEEK_SET); + } +} + +/* all this code just to support this little trivial filter! */ +static void +filter(CamelMimeFilter *mf, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace) +{ + CamelMimeFilterSave *f = (CamelMimeFilterSave *)mf; + + if (f->fd != -1) { + /* FIXME: check return */ + int outlen = write(f->fd, in, len); + if (outlen != len) { + g_warning("could not write to '%s': %s", f->filename?f->filename:"<descriptor>", strerror(errno)); + } + } + *out = in; + *outlen = len; + *outprespace = prespace; +} + +static void +camel_mime_filter_save_class_init (CamelMimeFilterSaveClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + CamelMimeFilterClass *filter_class = (CamelMimeFilterClass *) klass; + + camel_mime_filter_save_parent = gtk_type_class (camel_mime_filter_get_type ()); + + object_class->finalize = finalise; + + filter_class->reset = reset; + filter_class->filter = filter; + + gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL); +} + +static void +camel_mime_filter_save_init (CamelMimeFilterSave *f) +{ + f->fd = -1; +} + +/** + * camel_mime_filter_save_new: + * + * Create a new CamelMimeFilterSave object. + * + * Return value: A new CamelMimeFilterSave widget. + **/ +CamelMimeFilterSave * +camel_mime_filter_save_new (void) +{ + CamelMimeFilterSave *new = CAMEL_MIME_FILTER_SAVE ( gtk_type_new (camel_mime_filter_save_get_type ())); + return new; +} + +CamelMimeFilterSave * +camel_mime_filter_save_new_name (const char *name, int flags, int mode) +{ + CamelMimeFilterSave *new = NULL; + + new = camel_mime_filter_save_new(); + if (new) { + new->fd = open(name, flags, mode); + if (new->fd != -1) { + new->filename = g_strdup(name); + } else { + gtk_object_unref((GtkObject *)new); + new = NULL; + } + } + return new; +} + diff --git a/camel/camel-mime-filter-save.h b/camel/camel-mime-filter-save.h new file mode 100644 index 0000000000..970e849db6 --- /dev/null +++ b/camel/camel-mime-filter-save.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _CAMEL_MIME_FILTER_SAVE_H +#define _CAMEL_MIME_FILTER_SAVE_H + +#include <gtk/gtk.h> + +#include <camel/camel-mime-filter.h> + +#define CAMEL_MIME_FILTER_SAVE(obj) GTK_CHECK_CAST (obj, camel_mime_filter_save_get_type (), CamelMimeFilterSave) +#define CAMEL_MIME_FILTER_SAVE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, camel_mime_filter_save_get_type (), CamelMimeFilterSaveClass) +#define IS_CAMEL_MIME_FILTER_SAVE(obj) GTK_CHECK_TYPE (obj, camel_mime_filter_save_get_type ()) + +typedef struct _CamelMimeFilterSave CamelMimeFilterSave; +typedef struct _CamelMimeFilterSaveClass CamelMimeFilterSaveClass; + +struct _CamelMimeFilterSave { + CamelMimeFilter parent; + + struct _CamelMimeFilterSavePrivate *priv; + + char *filename; + int fd; +}; + +struct _CamelMimeFilterSaveClass { + CamelMimeFilterClass parent_class; +}; + +guint camel_mime_filter_save_get_type (void); +CamelMimeFilterSave *camel_mime_filter_save_new (void); + +CamelMimeFilterSave *camel_mime_filter_save_new_name (const char *name, int flags, int mode); + +#endif /* ! _CAMEL_MIME_FILTER_SAVE_H */ diff --git a/camel/camel-mime-filter.c b/camel/camel-mime-filter.c new file mode 100644 index 0000000000..de34a4c973 --- /dev/null +++ b/camel/camel-mime-filter.c @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "camel-mime-filter.h" + +struct _CamelMimeFilterPrivate { + char *inbuf; + size_t inlen; +}; + +#define PRE_HEAD (64) +#define BACK_HEAD (64) +#define _PRIVATE(o) (((CamelMimeFilter *)(o))->priv) +#define FCLASS(o) ((CamelMimeFilterClass *)((GtkObject *)(o))->klass) + +static void camel_mime_filter_class_init (CamelMimeFilterClass *klass); +static void camel_mime_filter_init (CamelMimeFilter *obj); + +static GtkObjectClass *camel_mime_filter_parent; + +enum SIGNALS { + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +guint +camel_mime_filter_get_type (void) +{ + static guint type = 0; + + if (!type) { + GtkTypeInfo type_info = { + "CamelMimeFilter", + sizeof (CamelMimeFilter), + sizeof (CamelMimeFilterClass), + (GtkClassInitFunc) camel_mime_filter_class_init, + (GtkObjectInitFunc) camel_mime_filter_init, + (GtkArgSetFunc) NULL, + (GtkArgGetFunc) NULL + }; + + type = gtk_type_unique (gtk_object_get_type (), &type_info); + } + + return type; +} + +static void +finalise(GtkObject *o) +{ + CamelMimeFilter *f = (CamelMimeFilter *)o; + struct _CamelMimeFilterPrivate *p = _PRIVATE(f); + + g_free(f->outreal); + g_free(f->backbuf); + g_free(p->inbuf); + g_free(p); + + ((GtkObjectClass *)camel_mime_filter_parent)->finalize (o); +} + +static void +complete(CamelMimeFilter *mf, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace) +{ + /* default - do nothing */ +} + +static void +camel_mime_filter_class_init (CamelMimeFilterClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + + camel_mime_filter_parent = gtk_type_class (gtk_object_get_type ()); + + object_class->finalize = finalise; + + klass->complete = complete; + + gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL); +} + +static void +camel_mime_filter_init (CamelMimeFilter *obj) +{ + obj->outreal = NULL; + obj->outbuf = NULL; + obj->outsize = 0; + + obj->backbuf = NULL; + obj->backsize = 0; + obj->backlen = 0; + + _PRIVATE(obj) = g_malloc0(sizeof(*obj->priv)); +} + +/** + * camel_mime_filter_new: + * + * Create a new CamelMimeFilter object. + * + * Return value: A new CamelMimeFilter widget. + **/ +CamelMimeFilter * +camel_mime_filter_new (void) +{ + CamelMimeFilter *new = CAMEL_MIME_FILTER ( gtk_type_new (camel_mime_filter_get_type ())); + return new; +} + +static void filter_run(CamelMimeFilter *f, + char *in, size_t len, size_t prespace, + char **out, size_t *outlen, size_t *outprespace, + void (*filterfunc)(CamelMimeFilter *f, + char *in, size_t len, size_t prespace, + char **out, size_t *outlen, size_t *outprespace)) +{ + struct _CamelMimeFilterPrivate *p; + + /* + here we take a performance hit, if the input buffer doesn't + have the pre-space required. We make a buffer that does ... + */ + if (prespace < f->backlen) { + int newlen = len+prespace; + p = _PRIVATE(f); + if (p->inlen < newlen) { + /* NOTE: g_realloc copies data, we dont need that (slower) */ + g_free(p->inbuf); + p->inbuf = g_malloc(newlen+PRE_HEAD); + p->inlen = newlen+PRE_HEAD; + } + /* copy to end of structure */ + memcpy(p->inbuf+p->inlen - len, in, len); + in = p->inbuf+p->inlen - len; + prespace = p->inlen - len; + } + + /* preload any backed up data */ + if (f->backlen > 0) { + memcpy(in-f->backlen, f->backbuf, f->backlen); + in -= f->backlen; + prespace -= f->backlen; + f->backlen = 0; + } + + filterfunc(f, in, len, prespace, out, outlen, outprespace); +} + +void camel_mime_filter_filter(CamelMimeFilter *f, + char *in, size_t len, size_t prespace, + char **out, size_t *outlen, size_t *outprespace) +{ + if (FCLASS(f)->filter) + filter_run(f, in, len, prespace, out, outlen, outprespace, FCLASS(f)->filter); + else + g_error("Filter function unplmenented in class"); +} + +void camel_mime_filter_complete(CamelMimeFilter *f, + char *in, size_t len, size_t prespace, + char **out, size_t *outlen, size_t *outprespace) +{ + if (FCLASS(f)->complete) + filter_run(f, in, len, prespace, out, outlen, outprespace, FCLASS(f)->complete); +} + +void camel_mime_filter_reset(CamelMimeFilter *f) +{ + if (FCLASS(f)->reset) { + FCLASS(f)->reset(f); + } + + /* could free some buffers, if they are really big? */ + f->backlen = 0; +} + +/* sets number of bytes backed up on the input, new calls replace previous ones */ +void camel_mime_filter_backup(CamelMimeFilter *f, char *data, size_t length) +{ + if (f->backsize < length) { + /* g_realloc copies data, unnecessary overhead */ + g_free(f->backbuf); + f->backbuf = g_malloc(length+BACK_HEAD); + f->backsize = length+BACK_HEAD; + } + f->backlen = length; + memcpy(f->backbuf, data, length); +} + +/* ensure this much size available for filter output (if required) */ +void camel_mime_filter_set_size(CamelMimeFilter *f, size_t size, int keep) +{ + if (f->outsize < size) { + int offset = f->outptr - f->outreal; + if (keep) { + f->outreal = g_realloc(f->outreal, size + PRE_HEAD*4); + } else { + g_free(f->outreal); + f->outreal = g_malloc(size + PRE_HEAD*4); + } + f->outptr = f->outreal + offset; + f->outbuf = f->outreal + PRE_HEAD*4; + f->outsize = size; + /* this could be offset from the end of the structure, but + this should be good enough */ + f->outpre = PRE_HEAD*4; + } +} + diff --git a/camel/camel-mime-filter.h b/camel/camel-mime-filter.h new file mode 100644 index 0000000000..51b63479de --- /dev/null +++ b/camel/camel-mime-filter.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Abstract class for non-copying filters */ + +#ifndef _CAMEL_MIME_FILTER_H +#define _CAMEL_MIME_FILTER_H + +#include <gtk/gtk.h> +#include <sys/types.h> + +#define CAMEL_MIME_FILTER(obj) GTK_CHECK_CAST (obj, camel_mime_filter_get_type (), CamelMimeFilter) +#define CAMEL_MIME_FILTER_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, camel_mime_filter_get_type (), CamelMimeFilterClass) +#define IS_CAMEL_MIME_FILTER(obj) GTK_CHECK_TYPE (obj, camel_mime_filter_get_type ()) + +typedef struct _CamelMimeFilter CamelMimeFilter; +typedef struct _CamelMimeFilterClass CamelMimeFilterClass; + +struct _CamelMimeFilter { + GtkObject parent; + + struct _CamelMimeFilterPrivate *priv; + + char *outreal; /* real malloc'd buffer */ + char *outbuf; /* first 'writable' position allowed (outreal + outpre) */ + char *outptr; + int outsize; + int outpre; /* prespace of this buffer */ + + char *backbuf; + int backsize; + int backlen; /* significant data there */ +}; + +struct _CamelMimeFilterClass { + GtkObjectClass parent_class; + + /* virtual functions */ + void (*filter)(CamelMimeFilter *f, + char *in, size_t len, size_t prespace, + char **out, size_t *outlen, size_t *outprespace); + void (*complete)(CamelMimeFilter *f, + char *in, size_t len, size_t prespace, + char **out, size_t *outlen, size_t *outprespace); + void (*reset)(CamelMimeFilter *f); +}; + +guint camel_mime_filter_get_type (void); +CamelMimeFilter *camel_mime_filter_new (void); + +void camel_mime_filter_filter(CamelMimeFilter *f, + char *in, size_t len, size_t prespace, + char **out, size_t *outlen, size_t *outprespace); + +void camel_mime_filter_complete(CamelMimeFilter *f, + char *in, size_t len, size_t prespace, + char **out, size_t *outlen, size_t *outprespace); + +void camel_mime_filter_reset(CamelMimeFilter *f); + +/* sets/returns number of bytes backed up on the input */ +void camel_mime_filter_backup(CamelMimeFilter *f, char *data, size_t length); + +/* ensure this much size available for filter output */ +void camel_mime_filter_set_size(CamelMimeFilter *f, size_t size, int keep); + +#endif /* ! _CAMEL_MIME_FILTER_H */ diff --git a/camel/camel-mime-parser.c b/camel/camel-mime-parser.c new file mode 100644 index 0000000000..cee6454707 --- /dev/null +++ b/camel/camel-mime-parser.c @@ -0,0 +1,1199 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* What should hopefully be a fast mail parser */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include <string.h> + +#include <stdio.h> +#include <errno.h> + +#include <unicode.h> + +#include "camel-mime-parser.h" +#include "camel-mime-utils.h" +#include "camel-mime-filter.h" +#include "camel-stream.h" +#include "camel-seekable-stream.h" + +#define r(x) +#define h(x) +#define c(x) +#define d(x) + +#define SCAN_BUF 4096 /* size of read buffer */ +#define SCAN_HEAD 128 /* headroom guaranteed to be before each read buffer */ + +/* a little hacky, but i couldn't be bothered renaming everything */ +#define _header_scan_state _CamelMimeParserPrivate +#define _PRIVATE(o) (((CamelMimeParser *)(o))->priv) + +struct _header_scan_state { + + /* global state */ + + enum _header_state state; + + /* for building headers during scanning */ + char *outbuf; + char *outptr; + char *outend; + + int fd; /* input for a fd input */ + CamelStream *stream; /* or for a stream */ + + /* for scanning input buffers */ + char *realbuf; /* the real buffer, SCAN_HEAD*2 + SCAN_BUF bytes */ + char *inbuf; /* points to a subset of the allocated memory, the underflow */ + char *inptr; /* (upto SCAN_HEAD) is for use by filters so they dont copy all data */ + char *inend; + + int atleast; + + int seek; /* current offset to start of buffer */ + + int midline; /* are we mid-line interrupted? */ + int scan_from; /* do we care about From lines? */ + + int start_of_from; /* where from started */ + int start_of_headers; /* where headers started from the last scan */ + + int header_start; /* start of last header, or -1 */ + + struct _header_scan_stack *top_part; /* top of message header */ + int top_start; /* offset of start */ + + struct _header_scan_stack *pending; /* if we're pending part info, from the wrong part end */ + + /* filters to apply to all content before output */ + int filterid; /* id of next filter */ + struct _header_scan_filter *filters; + + /* per message/part info */ + struct _header_scan_stack *parts; + +}; + +struct _header_scan_stack { + struct _header_scan_stack *parent; + + enum _header_state savestate; /* state at invocation of this part */ + + struct _header_raw *headers; /* headers for this part */ + + struct _header_content_type *content_type; + + char *boundary; /* for multipart/ * boundaries, including leading -- and trailing -- for the final part */ + int boundarylen; /* length of boundary, including leading -- */ +}; + +struct _header_scan_filter { + struct _header_scan_filter *next; + int id; + CamelMimeFilter *filter; +}; + +static void folder_scan_step(struct _header_scan_state *s, char **databuffer, int *datalength); +static int folder_scan_init_with_fd(struct _header_scan_state *s, int fd); +static int folder_scan_init_with_stream(struct _header_scan_state *s, CamelStream *stream); +static struct _header_scan_state *folder_scan_init(void); +static void folder_scan_close(struct _header_scan_state *s); +static struct _header_scan_stack *folder_scan_content(struct _header_scan_state *s, int *lastone, char **data, int *length); +static struct _header_scan_stack *folder_scan_header(struct _header_scan_state *s, int *lastone); +static int folder_scan_skip_line(struct _header_scan_state *s); +static off_t folder_seek(struct _header_scan_state *s, off_t offset, int whence); +static off_t folder_tell(struct _header_scan_state *s); + +static void camel_mime_parser_class_init (CamelMimeParserClass *klass); +static void camel_mime_parser_init (CamelMimeParser *obj); + +static char *states[] = { + "HSCAN_INITIAL", + "HSCAN_FROM", /* got 'From' line */ + "HSCAN_HEADER", /* toplevel header */ + "HSCAN_BODY", /* scanning body of message */ + "HSCAN_MULTIPART", /* got multipart header */ + "HSCAN_MESSAGE", /* rfc822 message */ + + "HSCAN_PART", /* part of a multipart */ + "<invalid>", + + "HSCAN_EOF", /* end of file */ + "HSCAN_FROM_END", + "HSCAN_HEAER_END", + "HSCAN_BODY_END", + "HSCAN_MULTIPART_END", + "HSCAN_MESSAGE_END", +}; + +static GtkObjectClass *camel_mime_parser_parent; + +enum SIGNALS { + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +guint +camel_mime_parser_get_type (void) +{ + static guint type = 0; + + if (!type) { + GtkTypeInfo type_info = { + "CamelMimeParser", + sizeof (CamelMimeParser), + sizeof (CamelMimeParserClass), + (GtkClassInitFunc) camel_mime_parser_class_init, + (GtkObjectInitFunc) camel_mime_parser_init, + (GtkArgSetFunc) NULL, + (GtkArgGetFunc) NULL + }; + + type = gtk_type_unique (gtk_object_get_type (), &type_info); + } + + return type; +} + +static void +finalise(GtkObject *o) +{ + struct _header_scan_state *s = _PRIVATE(o); + + folder_scan_close(s); + + ((GtkObjectClass *)camel_mime_parser_parent)->finalize (o); +} + +static void +camel_mime_parser_class_init (CamelMimeParserClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + + camel_mime_parser_parent = gtk_type_class (gtk_object_get_type ()); + + object_class->finalize = finalise; + + gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL); +} + +static void +camel_mime_parser_init (CamelMimeParser *obj) +{ + struct _header_scan_state *s; + + s = folder_scan_init(); + _PRIVATE(obj) = s; +} + +/** + * camel_mime_parser_new: + * + * Create a new CamelMimeParser object. + * + * Return value: A new CamelMimeParser widget. + **/ +CamelMimeParser * +camel_mime_parser_new (void) +{ + CamelMimeParser *new = CAMEL_MIME_PARSER ( gtk_type_new (camel_mime_parser_get_type ())); + return new; +} + + +int +camel_mime_parser_filter_add(CamelMimeParser *m, CamelMimeFilter *mf) +{ + struct _header_scan_state *s = _PRIVATE(m); + struct _header_scan_filter *f, *new; + + new = g_malloc(sizeof(*new)); + new->filter = mf; + new->id = s->filterid++; + if (s->filterid == -1) + s->filterid++; + new->next = 0; + gtk_object_ref((GtkObject *)mf); + + /* yes, this is correct, since 'next' is the first element of the struct */ + f = (struct _header_scan_filter *)&s->filters; + while (f->next) + f = f->next; + f->next = new; + return new->id; +} + +void +camel_mime_parser_filter_remove(CamelMimeParser *m, int id) +{ + struct _header_scan_state *s = _PRIVATE(m); + struct _header_scan_filter *f, *old; + + f = (struct _header_scan_filter *)&s->filters; + while (f && f->next) { + old = f->next; + if (old->id == id) { + gtk_object_unref((GtkObject *)old->filter); + f->next = old->next; + g_free(old); + /* there should only be a single matching id, but + scan the whole lot anyway */ + } + f = f->next; + } +} + +const char * +camel_mime_parser_header(CamelMimeParser *m, const char *name, int *offset) +{ + struct _header_scan_state *s = _PRIVATE(m); + + if (s->parts && + s->parts->headers) { + return header_raw_find(&s->parts->headers, name, offset); + } + return NULL; +} + +struct _header_raw * +camel_mime_parser_headers_raw(CamelMimeParser *m) +{ + struct _header_scan_state *s = _PRIVATE(m); + + if (s->parts) + return s->parts->headers; + return NULL; +} + +int +camel_mime_parser_init_with_fd(CamelMimeParser *m, int fd) +{ + struct _header_scan_state *s = _PRIVATE(m); + + return folder_scan_init_with_fd(s, fd); +} + +int +camel_mime_parser_init_with_stream(CamelMimeParser *m, CamelStream *stream) +{ + struct _header_scan_state *s = _PRIVATE(m); + + return folder_scan_init_with_stream(s, stream); +} + +void +camel_mime_parser_scan_from(CamelMimeParser *m, int scan_from) +{ + struct _header_scan_state *s = _PRIVATE(m); + s->scan_from = scan_from; +} + +struct _header_content_type * +camel_mime_parser_content_type(CamelMimeParser *m) +{ + struct _header_scan_state *s = _PRIVATE(m); + + /* FIXME: should this search up until its found the 'right' + content-type? can it? */ + if (s->parts) + return s->parts->content_type; + return NULL; +} + +enum _header_state +camel_mime_parser_step(CamelMimeParser *m, char **databuffer, int *datalength) +{ + struct _header_scan_state *s = _PRIVATE(m); + + folder_scan_step(s, databuffer, datalength); + return s->state; +} + +off_t camel_mime_parser_tell(CamelMimeParser *m) +{ + struct _header_scan_state *s = _PRIVATE(m); + + return folder_tell(s); +} + +off_t camel_mime_parser_tell_start_headers(CamelMimeParser *m) +{ + struct _header_scan_state *s = _PRIVATE(m); + + return s->start_of_headers; +} + +off_t camel_mime_parser_tell_start_from(CamelMimeParser *m) +{ + struct _header_scan_state *s = _PRIVATE(m); + + return s->start_of_from; +} + +off_t camel_mime_parser_seek(CamelMimeParser *m, off_t off, int whence) +{ + struct _header_scan_state *s = _PRIVATE(m); + return folder_seek(s, off, whence); +} + +/* ********************************************************************** */ +/* Implementation */ +/* ********************************************************************** */ + +/* read the next bit of data, ensure there is enough room 'atleast' bytes */ +static int +folder_read(struct _header_scan_state *s) +{ + int len; + int inoffset; + + if (s->inptr<s->inend-s->atleast) + return s->inend-s->inptr; + + /* check for any remaning bytes (under the atleast limit( */ + inoffset = s->inend - s->inptr; + if (inoffset>0) { + memcpy(s->inbuf, s->inptr, inoffset); + } + if (s->stream) { + len = camel_stream_read(s->stream, s->inbuf+inoffset, SCAN_BUF-inoffset); + } else { + len = read(s->fd, s->inbuf+inoffset, SCAN_BUF-inoffset); + } + r(printf("read %d bytes, offset = %d\n", len, inoffset)); + if (len>=0) { + /* add on the last read block */ + s->seek += s->inptr - s->inbuf; + s->inptr = s->inbuf; + s->inend = s->inbuf+len+inoffset; + r(printf("content = %d '%.*s'\n",s->inend - s->inptr, s->inend - s->inptr, s->inptr)); + } + r(printf("content = %d '%.*s'\n", s->inend - s->inptr, s->inend - s->inptr, s->inptr)); + return s->inend-s->inptr; +} + +/* return the current absolute position of the data pointer */ +static off_t +folder_tell(struct _header_scan_state *s) +{ + return s->seek + (s->inptr - s->inbuf); +} + +/* + need some way to prime the parser state, so this actually works for + other than top-level messages +*/ +static off_t +folder_seek(struct _header_scan_state *s, off_t offset, int whence) +{ + off_t newoffset; + int len; + + if (s->stream) { + if (CAMEL_IS_SEEKABLE_STREAM(s->stream)) { + /* NOTE: assumes whence seekable stream == whence libc, which is probably + the case (or bloody well should've been) */ + newoffset = camel_seekable_stream_seek((CamelSeekableStream *)s->stream, offset, whence); + } else { + newoffset = -1; + errno = EINVAL; + } + } else { + newoffset = lseek(s->fd, offset, whence); + } + if (newoffset != -1) { + s->seek = newoffset; + s->inptr = s->inbuf; + s->inend = s->inbuf; + if (s->stream) + len = camel_stream_read(s->stream, s->inbuf, SCAN_BUF); + else + len = read(s->fd, s->inbuf, SCAN_BUF); + if (len>=0) + s->inend = s->inbuf+len; + else + newoffset = -1; + } + return newoffset; +} + +static void +folder_push_part(struct _header_scan_state *s, struct _header_scan_stack *h) +{ + h->parent = s->parts; + s->parts = h; +} + +static void +folder_pull_part(struct _header_scan_state *s) +{ + struct _header_scan_stack *h; + + h = s->parts; + if (h) { + s->parts = h->parent; + g_free(h->boundary); + header_raw_clear(&h->headers); + header_content_type_unref(h->content_type); + g_free(h); + } else { + g_warning("Header stack underflow!\n"); + } +} + +static int +folder_scan_skip_line(struct _header_scan_state *s) +{ + int atleast = s->atleast; + register char *inptr, *inend, c; + int len; + + s->atleast = 1; + + while ( (len = folder_read(s)) > 0 && len > s->atleast) { /* ensure we have at least enough room here */ + inptr = s->inptr; + inend = s->inend-1; + + c = -1; + while (inptr<inend + && (c = *inptr++)!='\n') + ; + + s->inptr = inptr; + + if (c=='\n') { + s->atleast = atleast; + return 0; + } + } + + s->atleast = atleast; + + return -1; /* not found */ +} + +static struct _header_scan_stack * +folder_boundary_check(struct _header_scan_state *s, const char *boundary, int *lastone) +{ + struct _header_scan_stack *part; + int len = s->atleast-2; /* make sure we dont access past the buffer */ + + h(printf("checking boundary marker upto %d bytes\n", len)); + part = s->parts; + while (part) { + h(printf(" boundary: %s\n", part->boundary)); + h(printf(" against: '%.*s'\n", len, boundary)); + if (part->boundary + && part->boundarylen <= len + && memcmp(boundary, part->boundary, part->boundarylen)==0) { + h(printf("matched boundary: %s\n", part->boundary)); + /* again, make sure we're in range */ + if (part->boundarylen <= len+2) { + h(printf("checking lastone\n")); + *lastone = (boundary[part->boundarylen]=='-' + && boundary[part->boundarylen+1]=='-'); + } else { + h(printf("not enough room to check last one?\n")); + *lastone = FALSE; + } + /*printf("ok, we found it! : %s \n", (*lastone)?"Last one":"More to come?");*/ + return part; + } + part = part->parent; + } + return NULL; +} + +static struct _header_scan_stack * +folder_scan_header(struct _header_scan_state *s, int *lastone) +{ + int atleast = s->atleast; + register char *inptr, *inend; + char *start; + int len; + struct _header_scan_stack *part, *overpart = s->parts; + struct _header_scan_stack *h; + + h(printf("scanning first bit\n")); + + h = g_malloc0(sizeof(*h)); + + /* FIXME: this info should be cached ? */ + part = s->parts; + s->atleast = 5; + while (part) { + if (part->boundary) + s->atleast = MAX(s->atleast, part->boundarylen+2); + part = part->parent; + } +#if 0 + s->atleast = MAX(s->atleast, 5); + if (s->parts) + s->atleast = MAX(s->atleast, s->parts->boundarylen+2); +#endif + + *lastone = FALSE; +retry: + + while ((len = folder_read(s))>0 && len >= s->atleast) { /* ensure we have at least enough room here */ + inptr = s->inptr; + inend = s->inend-s->atleast; + start = inptr; + + while (inptr<=inend) { + register int c=-1; + /*printf(" '%.20s'\n", inptr);*/ + + if (!s->midline + && (part = folder_boundary_check(s, inptr, lastone))) { + if ((s->outptr>s->outbuf) || (inptr-start)) + goto header_truncated; /* may not actually be truncated */ + + goto normal_exit; + } + + /* goto next line */ + while (inptr<=inend && (c = *inptr++)!='\n') + ; + + /* allocate/append - this wont get executed unless we have *huge* headers, + and then probably only once */ + { + register int headerlen = inptr-start; + register int len = (s->outend - s->outbuf); + char *outnew; + + if (headerlen >= len) { + len = (len+headerlen)*2+1; + outnew = g_realloc(s->outbuf, len); + s->outptr = s->outptr - s->outbuf + outnew; + s->outbuf = outnew; + s->outend = outnew + len; + } + memcpy(s->outptr, start, headerlen); + s->outptr += headerlen; + } + + h(printf("outbuf[0] = %02x '%c' oubuf[1] = %02x '%c'\n", + s->outbuf[0], isprint(s->outbuf[0])?s->outbuf[0]:'.', + s->outbuf[1], isprint(s->outbuf[1])?s->outbuf[1]:'.')); + + if (s->header_start == -1) + s->header_start = (start-s->inbuf) + s->seek; + + if (c!='\n') { + s->midline = TRUE; + } else { + if (!(inptr[0] == ' ' || inptr[0] == '\t')) { + if (s->outbuf[0] == '\n' + || (s->outbuf[0] == '\r' && s->outbuf[1]=='\n')) { + goto header_done; + } + + s->outptr[0] = 0; + + d(printf("header %.10s at %d\n", s->outbuf, s->header_start)); + + header_raw_append_parse(&h->headers, s->outbuf, s->header_start); + if (inptr[0]=='\n' + || (inptr[0] == '\r' && inptr[1]=='\n')) { + inptr++; + goto header_done; + } + s->outptr = s->outbuf; + s->header_start = -1; + } + s->midline = FALSE; + start = inptr; + } + } + s->inptr = inptr; + } + + /* ok, we're at the end of the data, just make sure we're not missing out some small + truncated header markers */ + if (overpart) { + overpart = overpart->parent; + while (overpart) { + if (overpart->boundary && (overpart->boundarylen+2) < s->atleast) { + s->atleast = overpart->boundarylen+2; + h(printf("Retrying next smaller part ...\n")); + goto retry; + } + overpart = overpart->parent; + } + } + + if ((s->outptr > s->outbuf) || s->inend > s->inptr) { + start = s->inptr; + inptr = s->inend; + goto header_truncated; + } + + s->atleast = atleast; + + return h; + +header_truncated: + + { + register int headerlen = inptr-start; + register int len = (s->outend - s->outbuf); + char *outnew; + + if (headerlen >= len) { + len = (len+headerlen)*2+1; + outnew = g_realloc(s->outbuf, len); + s->outptr = s->outptr - s->outbuf + outnew; + s->outbuf = outnew; + s->outend = outnew + len; + } + memcpy(s->outptr, start, headerlen); + s->outptr += headerlen; + } + s->outptr[0] = 0; + + if (s->header_start == -1) + s->header_start = (start-s->inbuf) + s->seek; + + if (s->outbuf[0] == '\n' + || (s->outbuf[0] == '\r' && s->outbuf[1]=='\n')) { + goto header_done; + } + + header_raw_append_parse(&h->headers, s->outbuf, s->header_start); + +header_done: + part = s->parts; + + s->outptr = s->outbuf; +normal_exit: + s->inptr = inptr; + s->atleast = atleast; + s->header_start = -1; + return h; +} + +static struct _header_scan_stack * +folder_scan_content(struct _header_scan_state *s, int *lastone, char **data, int *length) +{ + int atleast = s->atleast; + register char *inptr, *inend; + char *start; + int len; + struct _header_scan_stack *part, *overpart = s->parts; + int already_packed = FALSE; + + /*printf("scanning content\n");*/ + + /* FIXME: this info should be cached ? */ + part = s->parts; + s->atleast = 5; + while (part) { + if (part->boundary) { + c(printf("boundary: %s\n", part->boundary)); + s->atleast = MAX(s->atleast, part->boundarylen+2); + } + part = part->parent; + } +/* s->atleast = MAX(s->atleast, 5);*/ +#if 0 + if (s->parts) + s->atleast = MAX(s->atleast, s->parts->boundarylen+2); +#endif + *lastone = FALSE; + +retry: + c(printf("atleast = %d\n", s->atleast)); + + while ((len = folder_read(s))>0 && len >= s->atleast) { /* ensure we have at least enough room here */ + inptr = s->inptr; + inend = s->inend-s->atleast; + start = inptr; + + c(printf("inptr = %p, inend = %p\n", inptr, inend)); + + while (inptr<=inend) { + if (!s->midline + && (part = folder_boundary_check(s, inptr, lastone))) { + if ( (inptr-start) ) + goto content; + + goto normal_exit; + } + /* goto the next line */ + while (inptr<=inend && (*inptr++)!='\n') + ; + + s->midline = FALSE; + } + + /* *sigh* so much for the beautiful simplicity of the code so far - here we + have the snot to deal with the nasty end-cases that come from the read-ahead + buffers we use */ + /* what this does, is if we are somewhere near the end of the buffer, + force it to the front, and re-read, ensuring we bunch as much together + as possible, for the final read, without copying too much of the time */ + /* make sure we dont loop forever, but also make sure we try smaller + boundaries, if there are any, so we dont miss any. */ + /* this is not needed for the header scanner, since it copies its own + data */ + c(printf("start offset = %d atleast = %d\n", start-s->inbuf, s->atleast)); + if (start > (s->inbuf + s->atleast)) { + /* force a re-scan of this data */ + s->inptr = start; + if (already_packed) + goto smaller_boundary; + c(printf("near the end, try and bunch things up a bit first\n")); + already_packed = TRUE; + } else { + c(printf("dumping what i've got ...\n")); + /* what would be nice here, is if that we're at eof, we bunch the last + little bit in the same content, but i dont think this is easy */ + goto content_mid; + } + } + + c(printf("length read = %d\n", len)); +smaller_boundary: + + /* ok, we're at the end of the data, just make sure we're not missing out some small + truncated header markers */ + if (overpart) { + overpart = overpart->parent; + while (overpart) { + if (overpart->boundary && (overpart->boundarylen+2) < s->atleast) { + s->atleast = overpart->boundarylen+2; + c(printf("Retrying next smaller part ...\n")); + goto retry; + } + overpart = overpart->parent; + } + } + + if (s->inend > s->inptr) { + start = s->inptr; + inptr = s->inend; + goto content; + } + + *length = 0; + s->atleast = atleast; + return NULL; + +content_mid: + s->midline = TRUE; +content: + part = s->parts; +normal_exit: + s->atleast = atleast; + s->inptr = inptr; + + *data = start; + *length = inptr-start; + +/* printf("got %scontent: %.*s", s->midline?"partial ":"", inptr-start, start);*/ + + return part; +} + + +static void +folder_scan_close(struct _header_scan_state *s) +{ + g_free(s->realbuf); + g_free(s->outbuf); + while (s->parts) + folder_pull_part(s); + g_free(s); +} + + +static struct _header_scan_state * +folder_scan_init(void) +{ + struct _header_scan_state *s; + + s = g_malloc(sizeof(*s)); + + s->fd = -1; + s->stream = NULL; + + s->outbuf = g_malloc(1024); + s->outptr = s->outbuf; + s->outend = s->outbuf+1024; + + s->realbuf = g_malloc(SCAN_BUF + SCAN_HEAD*2); + s->inbuf = s->realbuf + SCAN_HEAD; + s->inptr = s->inbuf; + s->inend = s->inbuf; + s->atleast = 0; + + s->seek = 0; /* current character position in file of the last read block */ + + s->header_start = -1; + + s->start_of_from = -1; + s->start_of_headers = -1; + + s->midline = FALSE; + s->scan_from = FALSE; + + s->filters = NULL; + s->filterid = 1; + + s->parts = NULL; + + s->state = HSCAN_INITIAL; + return s; +} + +static int +folder_scan_init_with_fd(struct _header_scan_state *s, int fd) +{ + int len; + + len = read(fd, s->inbuf, SCAN_BUF); + if (len>=0) { + s->inend = s->inbuf+len; + s->fd = fd; + if (s->stream) { + gtk_object_unref((GtkObject *)s->stream); + s->stream = NULL; + } + return 0; + } else { + return -1; + } +} + +static int +folder_scan_init_with_stream(struct _header_scan_state *s, CamelStream *stream) +{ + int len; + + len = camel_stream_read(stream, s->inbuf, SCAN_BUF); + if (len>=0) { + s->inend = s->inbuf+len; + s->stream = stream; + if (s->fd != -1) { + close(s->fd); + s->fd = -1; + } + return 0; + } else { + return -1; + } +} + +#define USE_FROM + +static void +folder_scan_step(struct _header_scan_state *s, char **databuffer, int *datalength) +{ + struct _header_scan_stack *h, *hb; + const char *content; + const char *bound; + int type; + int state; + struct _header_content_type *ct = NULL; + struct _header_scan_filter *f; + size_t presize; + +/* printf("\nSCAN PASS: state = %d '%s'\n", s->state, states[s->state]);*/ + +tail_recurse: + d({ + printf("\nSCAN STACK:\n"); + printf(" '%s' :\n", states[s->state]); + hb = s->parts; + while (hb) { + printf(" '%s' : %s\n", states[hb->savestate], hb->boundary); + hb = hb->parent; + } + printf("\n"); + }); + + switch (s->state) { + + case HSCAN_INITIAL: +#ifdef USE_FROM + if (s->scan_from) { + /* FIXME: it would be nice not to have to allocate this every pass */ + h = g_malloc0(sizeof(*h)); + h->boundary = g_strdup("From "); + h->boundarylen = strlen(h->boundary); + folder_push_part(s, h); + + h = s->parts; + do { + hb = folder_scan_content(s, &state, databuffer, datalength); + } while (hb==h && *datalength>0); + + if (*datalength==0 && hb==h) { + d(printf("found 'From '\n")); + s->start_of_from = folder_tell(s); + folder_scan_skip_line(s); + h->savestate = HSCAN_INITIAL; + s->state = HSCAN_FROM; + } else { + folder_pull_part(s); + s->state = HSCAN_EOF; + } + return; + } else { + s->start_of_from = -1; + } + +#endif + case HSCAN_FROM: + s->start_of_headers = folder_tell(s); + h = folder_scan_header(s, &state); +#ifdef USE_FROM + if (s->scan_from) + h->savestate = HSCAN_FROM_END; + else +#endif + h->savestate = HSCAN_EOF; + + /* FIXME: should this check for MIME-Version: 1.0 as well? */ + + type = HSCAN_HEADER; + if ( (content = header_raw_find(&h->headers, "Content-Type", NULL)) + && (ct = header_content_type_decode(content))) { + if (!strcasecmp(ct->type, "multipart")) { + bound = header_content_type_param(ct, "boundary"); + if (bound) { + d(printf("multipart, boundary = %s\n", bound)); + h->boundarylen = strlen(bound)+2; + h->boundary = g_malloc(h->boundarylen+3); + sprintf(h->boundary, "--%s--", bound); + type = HSCAN_MULTIPART; + } else { + g_warning("Multipart with no boundary, treating as text/plain"); + } + } else if (!strcasecmp(ct->type, "message")) { + if (!strcasecmp(ct->subtype, "rfc822") + /*|| !strcasecmp(ct->subtype, "partial")*/) { + type = HSCAN_MESSAGE; + } + } + } + h->content_type = ct; + folder_push_part(s, h); + s->state = type; + return; + + case HSCAN_HEADER: + s->state = HSCAN_BODY; + + case HSCAN_BODY: + h = s->parts; + *datalength = 0; + presize = SCAN_HEAD; + f = s->filters; + + do { + hb = folder_scan_content(s, &state, databuffer, datalength); + if (*datalength>0) { + d(printf("Content raw: '%.*s'\n", *datalength, *databuffer)); + + while (f) { + camel_mime_filter_filter(f->filter, *databuffer, *datalength, presize, + databuffer, datalength, &presize); + f = f->next; + } + return; + } + } while (hb==h && *datalength>0); + + /* check for any filter completion data */ + while (f) { + camel_mime_filter_filter(f->filter, *databuffer, *datalength, presize, + databuffer, datalength, &presize); + f = f->next; + } + if (*datalength > 0) + return; + + s->state = HSCAN_BODY_END; + break; + + case HSCAN_MULTIPART: + h = s->parts; + do { + do { + hb = folder_scan_content(s, &state, databuffer, datalength); + if (*datalength>0) { + /* FIXME: needs a state to return this shit??? */ + d(printf("Multipart Content: '%.*s'\n", *datalength, *databuffer)); + } + } while (hb==h && *datalength>0); + if (*datalength==0 && hb==h) { + d(printf("got boundary: %s\n", hb->boundary)); + folder_scan_skip_line(s); + if (!state) { + s->state = HSCAN_FROM; + folder_scan_step(s, databuffer, datalength); + s->parts->savestate = HSCAN_MULTIPART; /* set return state for the new head part */ + return; + } + } else { + break; + } + } while (1); + + s->state = HSCAN_MULTIPART_END; + break; + + case HSCAN_MESSAGE: + s->state = HSCAN_FROM; + folder_scan_step(s, databuffer, datalength); + s->parts->savestate = HSCAN_MESSAGE_END; + break; + + case HSCAN_FROM_END: + case HSCAN_BODY_END: + case HSCAN_MULTIPART_END: + case HSCAN_MESSAGE_END: + s->state = s->parts->savestate; + folder_pull_part(s); + if (s->state & HSCAN_END) + return; + goto tail_recurse; + + case HSCAN_EOF: + return; + + default: + g_warning("Invalid state in camel-mime-parser: %d", s->state); + break; + } + + return; +} + +#ifdef STANDALONE +int main(int argc, char **argv) +{ + int fd; + struct _header_scan_state *s; + char *data; + int len; + int state; + char *name = "/tmp/evmail/Inbox"; + struct _header_scan_stack *h; + int i; + int attach = 0; + + if (argc==2) + name = argv[1]; + + printf("opening: %s", name); + + for (i=1;i<argc;i++) { + const char *encoding = NULL, *charset = NULL; + char *attachname; + + name = argv[i]; + printf("opening: %s", name); + + fd = open(name, O_RDONLY); + if (fd==-1) { + perror("Cannot open mailbox"); + exit(1); + } + s = folder_scan_init(fd); + s->scan_from = FALSE; +#if 0 + h = g_malloc0(sizeof(*h)); + h->savestate = HSCAN_EOF; + folder_push_part(s, h); +#endif + while (s->state != HSCAN_EOF) { + folder_scan_step(s, &data, &len); + printf("\n -- PARSER STEP RETURN -- %d '%s'\n\n", s->state, states[s->state]); + switch (s->state) { + case HSCAN_HEADER: + if (s->parts->content_type + && (charset = header_content_type_param(s->parts->content_type, "charset"))) { + if (strcasecmp(charset, "us-ascii")) { + folder_push_filter_charset(s, "UTF-8", charset); + } else { + charset = NULL; + } + } else { + charset = NULL; + } + + encoding = header_raw_find(&s->parts->headers, "Content-transfer-encoding"); + printf("encoding = '%s'\n", encoding); + if (encoding && !strncasecmp(encoding, " base64", 7)) { + printf("adding base64 filter\n"); + attachname = g_strdup_printf("attach.%d.%d", i, attach++); + folder_push_filter_save(s, attachname); + g_free(attachname); + folder_push_filter_mime(s, 0); + } + if (encoding && !strncasecmp(encoding, " quoted-printable", 17)) { + printf("adding quoted-printable filter\n"); + attachname = g_strdup_printf("attach.%d.%d", i, attach++); + folder_push_filter_save(s, attachname); + g_free(attachname); + folder_push_filter_mime(s, 1); + } + + break; + case HSCAN_BODY: + break; + case HSCAN_BODY_END: + if (encoding && !strncasecmp(encoding, " base64", 7)) { + printf("removing filters\n"); + folder_filter_pull(s); + folder_filter_pull(s); + } + if (encoding && !strncasecmp(encoding, " quoted-printable", 17)) { + printf("removing filters\n"); + folder_filter_pull(s); + folder_filter_pull(s); + } + if (charset) { + folder_filter_pull(s); + charset = NULL; + } + encoding = NULL; + break; + default: + break; + } + } + folder_scan_close(s); + close(fd); + } + return 0; +} + +#endif /* STANDALONE */ diff --git a/camel/camel-mime-parser.h b/camel/camel-mime-parser.h new file mode 100644 index 0000000000..d42ea4709c --- /dev/null +++ b/camel/camel-mime-parser.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _CAMEL_MIME_PARSER_H +#define _CAMEL_MIME_PARSER_H + +#include <gtk/gtk.h> + +#include <camel/camel-mime-utils.h> +#include <camel/camel-mime-filter.h> +#include <camel/camel-stream.h> + +#define CAMEL_MIME_PARSER(obj) GTK_CHECK_CAST (obj, camel_mime_parser_get_type (), CamelMimeParser) +#define CAMEL_MIME_PARSER_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, camel_mime_parser_get_type (), CamelMimeParserClass) +#define IS_CAMEL_MIME_PARSER(obj) GTK_CHECK_TYPE (obj, camel_mime_parser_get_type ()) + +typedef struct _CamelMimeParser CamelMimeParser; +typedef struct _CamelMimeParserClass CamelMimeParserClass; + +/* NOTE: if you add more states, you may need to bump the + start of the END tags to 16 or 32, etc - so they are + the same as the matching start tag, with a bit difference */ +enum _header_state { + HSCAN_INITIAL, + HSCAN_FROM, /* got 'From' line */ + HSCAN_HEADER, /* toplevel header */ + HSCAN_BODY, /* scanning body of message */ + HSCAN_MULTIPART, /* got multipart header */ + HSCAN_MESSAGE, /* rfc822 message */ + + HSCAN_PART, /* part of a multipart */ + + HSCAN_END = 8, /* bit mask for 'end' flags */ + + HSCAN_EOF = 8, /* end of file */ + HSCAN_FROM_END, /* end of whole from bracket */ + HSCAN_HEADER_END, /* dummy value */ + HSCAN_BODY_END, /* end of message */ + HSCAN_MULTIPART_END, /* end of multipart */ + HSCAN_MESSAGE_END, /* end of message */ + +}; + +struct _CamelMimeParser { + GtkObject parent; + + struct _CamelMimeParserPrivate *priv; +}; + +struct _CamelMimeParserClass { + GtkObjectClass parent_class; + + void (*message)(CamelMimeParser *, void *headers); + void (*part)(CamelMimeParser *); + void (*content)(CamelMimeParser *); +}; + +guint camel_mime_parser_get_type (void); +CamelMimeParser *camel_mime_parser_new (void); + +/* using an fd will be a little faster, but not much (over a simple stream) */ +int camel_mime_parser_init_with_fd(CamelMimeParser *, int fd); +int camel_mime_parser_init_with_stream(CamelMimeParser *m, CamelStream *stream); + +/* scan 'From' separators? */ +void camel_mime_parser_scan_from(CamelMimeParser *, int); + +/* normal interface */ +enum _header_state camel_mime_parser_step(CamelMimeParser *, char **, int *); + +/* get content type for the current part/header */ +struct _header_content_type *camel_mime_parser_content_type(CamelMimeParser *); + +/* get a raw header by name */ +const char *camel_mime_parser_header(CamelMimeParser *, const char *, int *offset); +/* get all raw headers */ +struct _header_raw *camel_mime_parser_headers_raw(CamelMimeParser *); + +/* add a processing filter for body contents */ +int camel_mime_parser_filter_add(CamelMimeParser *, CamelMimeFilter *); +void camel_mime_parser_filter_remove(CamelMimeParser *, int); + +/* these should be used with caution, because the state will not + track the seeked position */ +/* FIXME: something to bootstrap the state? */ +off_t camel_mime_parser_tell(CamelMimeParser *); +off_t camel_mime_parser_seek(CamelMimeParser *, off_t, int); + +off_t camel_mime_parser_tell_start_headers(CamelMimeParser *); +off_t camel_mime_parser_tell_start_from(CamelMimeParser *); + +#endif /* ! _CAMEL_MIME_PARSER_H */ diff --git a/camel/camel-mime-part.c b/camel/camel-mime-part.c index ca4f71dc31..67be67d248 100644 --- a/camel/camel-mime-part.c +++ b/camel/camel-mime-part.c @@ -39,9 +39,9 @@ #include "hash-table-utils.h" #include "camel-stream-mem.h" #include "camel-mime-part-utils.h" -#include "gmime-base64.h" #include "camel-seekable-substream.h" -#include "camel-stream-b64.h" +#include "camel-stream-filter.h" +#include "camel-mime-filter-basic.h" typedef enum { @@ -237,13 +237,12 @@ my_finalize (GtkObject *object) { CamelMimePart *mime_part = CAMEL_MIME_PART (object); -#warning do something for (mime_part->disposition) which should not be a GMimeContentField - g_free (mime_part->description); g_free (mime_part->content_id); g_free (mime_part->content_MD5); string_list_free (mime_part->content_languages); g_free (mime_part->filename); + header_disposition_unref(mime_part->disposition); if (mime_part->header_lines) string_list_free (mime_part->header_lines); if (mime_part->content_type) gmime_content_field_unref (mime_part->content_type); @@ -313,15 +312,8 @@ camel_mime_part_get_description (CamelMimePart *mime_part) static void my_set_disposition (CamelMimePart *mime_part, const gchar *disposition) { -#warning Do not use MimeContentfield here !!! - - if (mime_part->disposition) { - g_free (mime_part->disposition->type); - g_free (mime_part->disposition); - } - - mime_part->disposition = g_new0 (GMimeContentField,1); - mime_part->disposition->type = g_strdup (disposition); + header_disposition_unref(mime_part->disposition); + mime_part->disposition = header_disposition_decode(disposition); } @@ -340,7 +332,7 @@ static const gchar * my_get_disposition (CamelMimePart *mime_part) { if (!mime_part->disposition) return NULL; - return (mime_part->disposition)->type; + return (mime_part->disposition)->disposition; } @@ -605,6 +597,7 @@ my_get_content_object (CamelMedium *medium) CamelMimePart *mime_part = CAMEL_MIME_PART (medium); CamelStream *stream; CamelStream *decoded_stream; + CamelMimeFilter *mf = NULL; if (!medium->content ) { stream = mime_part->content_input_stream; @@ -615,16 +608,23 @@ my_get_content_object (CamelMedium *medium) case CAMEL_MIME_PART_ENCODING_DEFAULT: case CAMEL_MIME_PART_ENCODING_7BIT: case CAMEL_MIME_PART_ENCODING_8BIT: + break; + case CAMEL_MIME_PART_ENCODING_QUOTEDPRINTABLE: + mf = (CamelMimeFilter *)camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_QP_DEC); break; case CAMEL_MIME_PART_ENCODING_BASE64: - decoded_stream = camel_stream_b64_new_with_input_stream (stream); - camel_stream_b64_set_mode (CAMEL_STREAM_B64 (decoded_stream), - CAMEL_STREAM_B64_DECODER); + mf = (CamelMimeFilter *)camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_BASE64_DEC); break; } + if (mf) { + decoded_stream = (CamelStream *)camel_stream_filter_new_with_stream (stream); + camel_stream_filter_add((CamelStreamFilter *)decoded_stream, mf); + gtk_object_unref((GtkObject *)mf); + } + camel_mime_part_construct_content_from_stream (mime_part, decoded_stream); } @@ -651,8 +651,8 @@ my_write_content_to_stream (CamelMimePart *mime_part, CamelStream *stream) { CamelMedium *medium; CamelStream *wrapper_stream; - CamelStreamB64 *stream_b64; - + CamelStream *stream_encode; + CamelMimeFilter *mf = NULL; CamelDataWrapper *content; g_assert (mime_part); @@ -672,18 +672,11 @@ my_write_content_to_stream (CamelMimePart *mime_part, CamelStream *stream) case CAMEL_MIME_PART_ENCODING_8BIT: camel_data_wrapper_write_to_stream (content, stream); break; + case CAMEL_MIME_PART_ENCODING_QUOTEDPRINTABLE: + mf = (CamelMimeFilter *)camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_QP_ENC); + break; case CAMEL_MIME_PART_ENCODING_BASE64: - /* encode the data wrapper output stream in base 64 ... */ - wrapper_stream = camel_data_wrapper_get_output_stream (content); - camel_stream_reset (wrapper_stream); - stream_b64 = CAMEL_STREAM_B64 (camel_stream_b64_new_with_input_stream (wrapper_stream)); - camel_stream_b64_set_mode (stream_b64, CAMEL_STREAM_B64_ENCODER); - - /* ... and write it to the output stream in a blocking way */ - camel_stream_b64_write_to_stream (stream_b64, stream); - - /* now free the intermediate b64 stream */ - gtk_object_unref (GTK_OBJECT (stream_b64)); + mf = (CamelMimeFilter *)camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_BASE64_ENC); break; default: camel_data_wrapper_write_to_stream (content, stream); @@ -691,6 +684,21 @@ my_write_content_to_stream (CamelMimePart *mime_part, CamelStream *stream) camel_mime_part_encoding_to_string (mime_part->encoding)); } + + if (mf) { + /* encode the data wrapper output stream in the filtered encoding */ + wrapper_stream = camel_data_wrapper_get_output_stream (content); + camel_stream_reset (wrapper_stream); + stream_encode = camel_stream_filter_new_with_stream (wrapper_stream); + camel_stream_filter_add((CamelStreamFilter *)stream_encode, mf); + + /* ... and write it to the output stream in a blocking way */ + camel_stream_write_to_stream (stream_encode, stream); + + /* now free the intermediate b64 stream */ + gtk_object_unref (GTK_OBJECT (stream_encode)); + gtk_object_unref((GtkObject *)mf); + } } @@ -703,7 +711,18 @@ my_write_to_stream (CamelDataWrapper *data_wrapper, CamelStream *stream) CamelMimePart *mp = CAMEL_MIME_PART (data_wrapper); CamelMedium *medium = CAMEL_MEDIUM (data_wrapper); - gmime_content_field_write_to_stream(mp->disposition, stream); + if (mp->disposition) { + struct _header_param *p; + + camel_stream_write_strings(stream, "Content-Disposition: ", mp->disposition->disposition, NULL); + /* FIXME: use proper quoting rules here ... */ + p = mp->disposition->params; + while (p) { + camel_stream_write_strings (stream, ";\n ", p->name, "= \"", p->value, "\"", NULL); + p = p->next; + } + camel_stream_write_string (stream, "\n"); + } WHPT (stream, "Content-Transfer-Encoding", camel_mime_part_encoding_to_string (mp->encoding)); WHPT (stream, "Content-Description", mp->description); @@ -848,9 +867,6 @@ my_get_output_stream (CamelDataWrapper *data_wrapper) return input_stream; case CAMEL_MIME_PART_ENCODING_BASE64: - output_stream = camel_stream_b64_new_with_input_stream (input_stream); - camel_stream_b64_set_mode (CAMEL_STREAM_B64 (output_stream), - CAMEL_STREAM_B64_ENCODER); return output_stream; case CAMEL_MIME_PART_ENCODING_QUOTEDPRINTABLE: diff --git a/camel/camel-mime-part.h b/camel/camel-mime-part.h index a5f956ffe5..37118c8384 100644 --- a/camel/camel-mime-part.h +++ b/camel/camel-mime-part.h @@ -37,6 +37,7 @@ extern "C" { #include <gtk/gtk.h> #include "camel-types.h" #include "camel-medium.h" +#include <camel/camel-mime-utils.h> #define CAMEL_MIME_PART_TYPE (camel_mime_part_get_type ()) #define CAMEL_MIME_PART(obj) (GTK_CHECK_CAST((obj), CAMEL_MIME_PART_TYPE, CamelMimePart)) @@ -63,7 +64,7 @@ struct _CamelMimePart /* All fields here are -** PRIVATE **- */ gchar *description; - GMimeContentField *disposition; + CamelMimeDisposition *disposition; gchar *content_id; gchar *content_MD5; GList *content_languages; diff --git a/camel/camel-mime-utils.c b/camel/camel-mime-utils.c new file mode 100644 index 0000000000..d3e2406f1a --- /dev/null +++ b/camel/camel-mime-utils.c @@ -0,0 +1,1870 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> + +#include <iconv.h> + +#include <glib.h> +#include <time.h> + +#include <ctype.h> + +#include "camel-mime-utils.h" + +#define d(x) +#define d2(x) + +static char *base64_alphabet = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static unsigned char tohex[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' +}; + +static unsigned char camel_mime_special_table[256] = { + 5, 5, 5, 5, 5, 5, 5, 5, 5,167, 7, 5, 5, 39, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 178,128,140,128,128,128,128,128,140,140,128,128,140,128,136,132, + 128,128,128,128,128,128,128,128,128,128,204,140,140, 4,140,132, + 140,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,172,172,172,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, 5, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static unsigned char camel_mime_base64_rank[256] = { + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255, + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +}; + +/* + if any of these change, then the tables above should be regenerated + by compiling this with -DBUILD_TABLE, and running. + + gcc -o buildtable `glib-config --cflags --libs` -DBUILD_TABLE camel-mime-utils.c + ./buildtable + +*/ +enum { + IS_CTRL = 1<<0, + IS_LWSP = 1<<1, + IS_TSPECIAL = 1<<2, + IS_SPECIAL = 1<<3, + IS_SPACE = 1<<4, + IS_DSPECIAL = 1<<5, + IS_COLON = 1<<6, /* rather wasteful of space ... */ + IS_QPSAFE = 1<<7 +}; + +#define is_ctrl(x) ((camel_mime_special_table[(unsigned char)(x)] & IS_CTRL) != 0) +#define is_lwsp(x) ((camel_mime_special_table[(unsigned char)(x)] & IS_LWSP) != 0) +#define is_tspecial(x) ((camel_mime_special_table[(unsigned char)(x)] & IS_TSPECIAL) != 0) +#define is_type(x, t) ((camel_mime_special_table[(unsigned char)(x)] & (t)) != 0) +#define is_ttoken(x) ((camel_mime_special_table[(unsigned char)(x)] & (IS_TSPECIAL|IS_LWSP|IS_CTRL)) == 0) +#define is_atom(x) ((camel_mime_special_table[(unsigned char)(x)] & (IS_SPECIAL|IS_SPACE|IS_CTRL)) == 0) +#define is_dtext(x) ((camel_mime_special_table[(unsigned char)(x)] & IS_DSPECIAL) == 0) +#define is_fieldname(x) ((camel_mime_special_table[(unsigned char)(x)] & (IS_CTRL|IS_SPACE|IS_COLON)) == 0) +#define is_qpsafe(x) ((camel_mime_special_table[(unsigned char)(x)] & IS_QPSAFE) != 0) + +/* only needs to be run to rebuild the tables above */ +#ifdef BUILD_TABLE + +#define CHARS_LWSP " \t\n\r" +#define CHARS_TSPECIAL "()<>@,;:\\\"/[]?=" +#define CHARS_SPECIAL "()<>@,;:\\\".[]" +#define CHARS_CSPECIAL "()\\\r" /* not in comments */ +#define CHARS_DSPECIAL "[]\\\r \t" /* not in domains */ + +static void +header_init_bits(unsigned char bit, unsigned char bitcopy, int remove, unsigned char *vals, int len) +{ + int i; + + if (!remove) { + for (i=0;i<len;i++) { + camel_mime_special_table[vals[i]] |= bit; + } + if (bitcopy) { + for (i=0;i<256;i++) { + if (camel_mime_special_table[i] & bitcopy) + camel_mime_special_table[i] |= bit; + } + } + } else { + for (i=0;i<256;i++) + camel_mime_special_table[i] |= bit; + for (i=0;i<len;i++) { + camel_mime_special_table[vals[i]] &= ~bit; + } + if (bitcopy) { + for (i=0;i<256;i++) { + if (camel_mime_special_table[i] & bitcopy) + camel_mime_special_table[i] &= ~bit; + } + } + } +} + +static void +header_decode_init(void) +{ + int i; + + for (i=0;i<256;i++) camel_mime_special_table[i] = 0; + for (i=0;i<32;i++) camel_mime_special_table[i] |= IS_CTRL; + camel_mime_special_table[127] = IS_CTRL; + camel_mime_special_table[' '] = IS_SPACE; + camel_mime_special_table[':'] = IS_COLON; + header_init_bits(IS_LWSP, 0, 0, CHARS_LWSP, sizeof(CHARS_LWSP)-1); + header_init_bits(IS_TSPECIAL, IS_CTRL, 0, CHARS_TSPECIAL, sizeof(CHARS_TSPECIAL)-1); + header_init_bits(IS_SPECIAL, 0, 0, CHARS_SPECIAL, sizeof(CHARS_SPECIAL)-1); + header_init_bits(IS_DSPECIAL, 0, FALSE, CHARS_DSPECIAL, sizeof(CHARS_DSPECIAL)-1); + for (i=0;i<256;i++) if ((i>=33 && i<=60) || (i>=62 && i<=126) || i==32 || i==9) camel_mime_special_table[i] |= IS_QPSAFE; +} + +void +base64_init(void) +{ + int i; + + memset(camel_mime_base64_rank, 0xff, sizeof(camel_mime_base64_rank)); + for (i=0;i<64;i++) { + camel_mime_base64_rank[(unsigned int)base64_alphabet[i]] = i; + } + camel_mime_base64_rank['='] = 0; +} + +int main(int argc, char **argv) +{ + int i; + void run_test(void); + + header_decode_init(); + base64_init(); + + printf("static unsigned char camel_mime_special_table[256] = {\n\t"); + for (i=0;i<256;i++) { + printf("%3d,", camel_mime_special_table[i]); + if ((i&15) == 15) { + printf("\n"); + if (i!=255) { + printf("\t"); + } + } + } + printf("};\n"); + + printf("static unsigned char camel_mime_base64_rank[256] = {\n\t"); + for (i=0;i<256;i++) { + printf("%3d,", camel_mime_base64_rank[i]); + if ((i&15) == 15) { + printf("\n"); + if (i!=255) { + printf("\t"); + } + } + } + printf("};\n"); + + run_test(); + + return 0; +} + +#endif + + +/* call this when finished encoding everything, to + flush off the last little bit */ +int +base64_encode_close(unsigned char *in, int inlen, unsigned char *out, int *state, int *save) +{ + int c1, c2; + unsigned char *outptr = out; + + if (inlen>0) + outptr += base64_encode_step(in, inlen, outptr, state, save); + + c1 = ((char *)save)[1]; + c2 = ((char *)save)[2]; + + switch (((char *)save)[0]) { + case 2: + outptr[2] = base64_alphabet [ ( (c2 &0x0f) << 2 ) ]; + goto skip; + case 1: + outptr[2] = '='; + skip: + outptr[0] = base64_alphabet [ c1 >> 2 ]; + outptr[1] = base64_alphabet [ c2 >> 4 | ( (c1&0x3) << 4 )]; + outptr[3] = '='; + outptr += 4; + break; + } + *outptr++ = '\n'; + + *save = 0; + *state = 0; + + return outptr-out; +} + +/* + performs an 'encode step', only encodes blocks of 3 characters to the + output at a time, saves left-over state in state and save (initialise to + 0 on first invocation). +*/ +int +base64_encode_step(unsigned char *in, int len, unsigned char *out, int *state, int *save) +{ + register unsigned char *inptr, *outptr; + + if (len<=0) + return 0; + + inptr = in; + outptr = out; + + d(printf("we have %d chars, and %d saved chars\n", len, ((char *)save)[0])); + + if (len + ((char *)save)[0] > 2) { + unsigned char *inend = in+len-2; + register int c1, c2, c3; + register int already; + + already = *state; + + switch (((char *)save)[0]) { + case 1: c1 = ((char *)save)[1]; goto skip1; + case 2: c1 = ((char *)save)[1]; + c2 = ((char *)save)[2]; goto skip2; + } + + /* yes, we jump into the loop, no i'm not going to change it, its beautiful! */ + while (inptr < inend) { + c1 = *inptr++; + skip1: + c2 = *inptr++; + skip2: + c3 = *inptr++; + *outptr++ = base64_alphabet [ c1 >> 2 ]; + *outptr++ = base64_alphabet [ c2 >> 4 | ( (c1&0x3) << 4 ) ]; + *outptr++ = base64_alphabet [ ( (c2 &0x0f) << 2 ) | (c3 >> 6) ]; + *outptr++ = base64_alphabet [ c3 & 0x3f ]; + /* this is a bit ugly ... */ + if ((++already)>=19) { + *outptr++='\n'; + already = 0; + } + } + + ((char *)save)[0] = 0; + len = 2-(inptr-inend); + *state = already; + } + + d(printf("state = %d, len = %d\n", + (int)((char *)save)[0], + len)); + + if (len>0) { + register char *saveout; + + /* points to the slot for the next char to save */ + saveout = & (((char *)save)[1]) + ((char *)save)[0]; + + /* len can only be 0 1 or 2 */ + switch(len) { + case 2: *saveout++ = *inptr++; + case 1: *saveout++ = *inptr++; + } + ((char *)save)[0]+=len; + } + + d(printf("mode = %d\nc1 = %c\nc2 = %c\n", + (int)((char *)save)[0], + (int)((char *)save)[1], + (int)((char *)save)[2])); + + return outptr-out; +} + +int +base64_decode_step(unsigned char *in, int len, unsigned char *out, int *state, unsigned int *save) +{ + register unsigned char *inptr, *outptr; + unsigned char *inend, c; + register unsigned int v; + int i; + + inend = in+len; + outptr = out; + + /* convert 4 base64 bytes to 3 normal bytes */ + v=*save; + i=*state; + inptr = in; + while (inptr<inend) { + c = camel_mime_base64_rank[*inptr++]; + if (c != 0xff) { + v = (v<<6) | c; + i++; + if (i==4) { + *outptr++ = v>>16; + *outptr++ = v>>8; + *outptr++ = v; + i=0; + } + } + } + + *save = v; + *state = i; + + /* quick scan back for '=' on the end somewhere */ + /* fortunately we can drop 1 output char for each trailing = (upto 2) */ + i=2; + while (inptr>in && i) { + inptr--; + if (camel_mime_base64_rank[*inptr] != 0xff) { + if (*inptr == '=') + outptr--; + i--; + } + } + + /* if i!= 0 then there is a truncation error! */ + return outptr-out; +} + +int +quoted_encode_close(unsigned char *in, int len, unsigned char *out, int *state, int *save) +{ + register unsigned char *outptr = out; + + if (len>0) + outptr += quoted_encode_step(in, len, outptr, state, save); + + /* hmm, not sure if this should really be added here, we dont want + to add it to the content, afterall ...? */ + *outptr++ = '\n'; + + *save = 0; + *state = 0; + + return outptr-out; +} + +/* + FIXME: does not handle trailing spaces/tabs before end of line +*/ +int +quoted_encode_step(unsigned char *in, int len, unsigned char *out, int *state, int *save) +{ + register unsigned char *inptr, *outptr, *inend; + unsigned char c; + register int sofar = *state; + + inptr = in; + inend = in+len; + outptr = out; + while (inptr<inend) { + c = *inptr++; + if (is_qpsafe(c)) { + /* check for soft line-break */ + if ((++sofar)>74) { + *outptr++='='; + *outptr++='\n'; + sofar = 1; + } + *outptr++=c; + } else { + if ((++sofar)>72) { + *outptr++='='; + *outptr++='\n'; + sofar = 3; + } + *outptr++ = '='; + *outptr++ = tohex[(c>>4) & 0xf]; + *outptr++ = tohex[c & 0xf]; + } + } + *state = sofar; + return outptr-out; +} + +/* + FIXME: this does not strip trailing spaces from lines (as it should, rfc 2045, section 6.7) + Should it also canonicalise the end of line to CR LF?? + + Note: Trailing rubbish (at the end of input), like = or =x or =\r will be lost. +*/ + +int +quoted_decode_step(unsigned char *in, int len, unsigned char *out, int *savestate, int *saveme) +{ + register unsigned char *inptr, *outptr; + unsigned char *inend, c; + int state, save; + + inend = in+len; + outptr = out; + + d(printf("quoted-printable, decoding text '%.*s'\n", len, in)); + + state = *savestate; + save = *saveme; + inptr = in; + while (inptr<inend) { + switch (state) { + case 0: + while (inptr<inend) { + c = *inptr++; + /* FIXME: use a specials table to avoid 3 comparisons for the common case */ + if (c=='=') { + state = 1; + break; + } +#ifdef CANONICALISE_EOL + /*else if (c=='\r') { + state = 3; + } else if (c=='\n') { + *outptr++ = '\r'; + *outptr++ = c; + } */ +#endif + else { + *outptr++ = c; + } + } + break; + case 1: + c = *inptr++; + if (c=='\n') { + /* soft break ... unix end of line */ + state = 0; + } else { + save = c; + state = 2; + } + break; + case 2: + c = *inptr++; + if (isxdigit(c) && isxdigit(save)) { + c = toupper(c); + save = toupper(save); + *outptr++ = (((save>='A'?save-'A'+10:save-'0')&0x0f) << 4) + | ((c>='A'?c-'A'+10:c-'0')&0x0f); + } else if (c=='\n' && save == '\r') { + /* soft break ... canonical end of line */ + } else { + /* just output the data */ + *outptr++ = '='; + *outptr++ = save; + *outptr++ = c; + } + state = 0; + break; +#ifdef CANONICALISE_EOL + case 3: + /* convert \r -> to \r\n, leaves \r\n alone */ + c = *inptr++; + if (c=='\n') { + *outptr++ = '\r'; + *outptr++ = c; + } else { + *outptr++ = '\r'; + *outptr++ = '\n'; + *outptr++ = c; + } + state = 0; + break; +#endif + } + } + + *savestate = state; + *saveme = save; + + return outptr-out; +} + +/* + this is for the "Q" encoding of international words, + which is slightly different than plain quoted-printable +*/ +int +quoted_decode(unsigned char *in, int len, unsigned char *out) +{ + register unsigned char *inptr, *outptr; + unsigned char *inend, c, c1; + int ret = 0; + + inend = in+len; + outptr = out; + + d(printf("decoding text '%.*s'\n", len, in)); + + inptr = in; + while (inptr<inend) { + c = *inptr++; + if (c=='=') { + /* silently ignore truncated data? */ + if (inend-in>=2) { + c = toupper(*inptr++); + c1 = toupper(*inptr++); + *outptr++ = (((c>='A'?c-'A'+10:c-'0')&0x0f) << 4) + | ((c1>='A'?c1-'A'+10:c1-'0')&0x0f); + } else { + ret = -1; + break; + } + } else if (c=='_') { + *outptr++ = 0x20; + } else if (c==' ' || c==0x09) { + /* FIXME: this is an error! ignore for now ... */ + ret = -1; + break; + } else { + *outptr++ = c; + } + } + if (ret==0) { + return outptr-out; + } + return -1; +} + + +static void +header_decode_lwsp(const char **in) +{ + const char *inptr = *in; + char c; + + d2(printf("is ws: '%s'\n", *in)); + + while (is_lwsp(*inptr) || *inptr =='(') { + while (is_lwsp(*inptr)) { + d2(printf("(%c)", *inptr)); + inptr++; + } + d2(printf("\n")); + + /* check for comments */ + if (*inptr == '(') { + int depth = 1; + inptr++; + while (depth && (c=*inptr)) { + if (c=='\\' && inptr[1]) { + inptr++; + } else if (c=='(') { + depth++; + } else if (c==')') { + depth--; + } + inptr++; + } + } + } + *in = inptr; +} + +char * +g_strdup_len(const char *start, int len) +{ + char *d = g_malloc(len+1); + memcpy(d, start, len); + d[len] = 0; + return d; +} + + +/* decode rfc 2047 encoded string segment */ +char * +rfc2047_decode_word(char *in, int len) +{ + char *inptr = in+2; + char *inend = in+len-2; + char *encname; + int tmplen; + int ret; + char *decword = NULL; + char *decoded = NULL; + char *outbase = NULL; + char *inbuf, *outbuf; + int inlen, outlen; + iconv_t ic; + + d(printf("decoding '%.*s'\n", len, in)); + + /* just make sure we're not passed shit */ + if (len<7 + || !(in[0]=='=' && in[1]=='?' && in[len-1]=='=' && in[len-2]=='?')) { + d(printf("invalid\n")); + return NULL; + } + + inptr = memchr(inptr, '?', inend-inptr); + if (inptr!=NULL + && inptr<inend+2 + && inptr[2]=='?') { + inptr++; + tmplen = inend-inptr-2; + decword = g_malloc(tmplen); /* this will always be more-than-enough room */ + switch(toupper(inptr[0])) { + case 'Q': + inlen = quoted_decode(inptr+2, tmplen, decword); + break; + case 'B': { + int state=0; + unsigned int save=0; + inlen = base64_decode_step(inptr+2, tmplen, decword, &state, &save); + /* if state != 0 then error? */ + break; + } + } + if (inlen>0) { + /* yuck, all this snot is to setup iconv! */ + tmplen = inptr-in-3; + encname = alloca(tmplen+1); + encname[tmplen]=0; + memcpy(encname, in+2, tmplen); + + inbuf = decword; + + outlen = inlen*6; + outbase = g_malloc(outlen); + outbuf = outbase; + + ic = iconv_open("utf-8", encname); + ret = iconv(ic, (const char **)&inbuf, &inlen, &outbuf, &outlen); + iconv_close(ic); + if (ret>=0) { + *outbuf = 0; + decoded = outbase; + outbase = NULL; + } + } + } + free(outbase); + free(decword); + + d(printf("decoded '%s'\n", decoded)); + + return decoded; +} + +char * +decode_coded_string(char *in) +{ + return rfc2047_decode_word(in, strlen(in)); +} + +/* grrr, glib should have this ! */ +GString * +g_string_append_len(GString *st, char *s, int l) +{ + char *tmp; + + tmp = alloca(l+1); + tmp[l]=0; + memcpy(tmp, s, l); + return g_string_append(st, tmp); +} + +/* decodes a simple text, rfc822 */ +char * +header_decode_text(char *in, int inlen) +{ + GString *out; + char *inptr = in; + char *inend = in+inlen; + char *encstart, *encend; + char *decword; + + out = g_string_new(""); + while ( (encstart = strstr(inptr, "=?")) + && (encend = strstr(encstart+2, "?=")) ) { + + decword = rfc2047_decode_word(encstart, encend-encstart+2); + if (decword) { + g_string_append_len(out, inptr, encstart-inptr); + g_string_append_len(out, decword, strlen(decword)); + free(decword); + } else { + g_string_append_len(out, inptr, encend-inptr+2); + } + inptr = encend+2; + } + g_string_append_len(out, inptr, inend-inptr); + + inptr = out->str; + g_string_free(out, FALSE); + return inptr; +} + +char * +header_decode_string(const char *in) +{ + if (in == NULL) + return NULL; + return header_decode_text(in, strlen(in)); +} + + +/* these are all internal parser functions */ + +char * +header_decode_token(const char **in) +{ + const char *inptr = *in; + const char *start; + + header_decode_lwsp(&inptr); + start = inptr; + while (is_ttoken(*inptr)) + inptr++; + if (inptr>start) { + *in = inptr; + return g_strdup_len(start, inptr-start); + } else { + return NULL; + } +} + +/* + <"> * ( <any char except <"> \, cr / \ <any char> ) <"> +*/ +static char * +header_decode_quoted_string(const char **in) +{ + const char *inptr = *in; + char *out = NULL, *outptr; + int outlen; + int c; + + header_decode_lwsp(&inptr); + if (*inptr == '"') { + const char *intmp; + int skip = 0; + + /* first, calc length */ + inptr++; + intmp = inptr; + while ( (c = *intmp++) && c!= '"' ) { + if (c=='\\' && *intmp) { + intmp++; + skip++; + } + } + outlen = intmp-inptr-skip; + out = outptr = g_malloc(outlen+1); + while ( (c = *inptr++) && c!= '"' ) { + if (c=='\\' && *inptr) { + c = *inptr++; + } + *outptr++ = c; + } + *outptr = 0; + } + *in = inptr; + return out; +} + +static char * +header_decode_atom(const char **in) +{ + const char *inptr = *in, *start; + + header_decode_lwsp(&inptr); + start = inptr; + while (is_atom(*inptr)) + inptr++; + *in = inptr; + if (inptr > start) + return g_strdup_len(start, inptr-start); + else + return NULL; +} + +static char * +header_decode_word(const char **in) +{ + const char *inptr = *in; + + header_decode_lwsp(&inptr); + if (*inptr == '"') { + *in = inptr; + return header_decode_quoted_string(in); + } else { + *in = inptr; + return header_decode_atom(in); + } +} + +static char * +header_decode_value(const char **in) +{ + const char *inptr = *in; + + header_decode_lwsp(&inptr); + if (*inptr == '"') { + d(printf("decoding quoted string\n")); + return header_decode_quoted_string(in); + } else if (is_ttoken(*inptr)) { + d(printf("decoding token\n")); + /* this may not have the right specials for all params? */ + return header_decode_token(in); + } + return NULL; +} + +/* shoudl this return -1 for no int? */ +static int +header_decode_int(const char **in) +{ + const char *inptr = *in; + int c, v=0; + + header_decode_lwsp(&inptr); + while ( (c=*inptr++ & 0xff) + && isdigit(c) ) { + v = v*10+(c-'0'); + } + *in = inptr-1; + return v; +} + +static int +header_decode_param(const char **in, char **paramp, char **valuep) +{ + const char *inptr = *in; + char *param, *value=NULL; + + param = header_decode_token(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == '=') { + inptr++; + value = header_decode_value(&inptr); + } + + if (param && value) { + *paramp = param; + *valuep = value; + *in = inptr; + return 0; + } else { + g_free(param); + g_free(value); + return 1; + } +} + +char * +header_param(struct _header_param *p, char *name) +{ + while (p && strcasecmp(p->name, name) != 0) + p = p->next; + if (p) + return p->value; + return NULL; +} + +struct _header_param * +header_set_param(struct _header_param **l, const char *name, const char *value) +{ + struct _header_param *p = (struct _header_param *)l, *pn; + + while (p->next) { + pn = p->next; + if (!strcasecmp(pn->name, name)) { + g_free(pn->value); + pn->value = g_strdup(value); + return pn; + } + p = pn; + } + + pn = g_malloc(sizeof(*pn)); + pn->next = 0; + pn->name = g_strdup(name); + pn->value = g_strdup(value); + p->next = pn; + + return pn; +} + +const char * +header_content_type_param(struct _header_content_type *t, const char *name) +{ + if (t==NULL) + return NULL; + return header_param(t->params, name); +} + +void header_content_type_set_param(struct _header_content_type *t, const char *name, const char *value) +{ + header_set_param(&t->params, name, value); +} + +/** + * header_content_type_is: + * @ct: A content type specifier, or #NULL. + * @type: A type to check against. + * @subtype: A subtype to check against, or "*" to match any subtype. + * + * Returns #TRUE if the content type @ct is of type @type/@subtype. + * The subtype of "*" will match any subtype. If @ct is #NULL, then + * it will match the type "text/plain". + * + * Return value: #TRUE or #FALSE depending on the matching of the type. + **/ +int +header_content_type_is(struct _header_content_type *ct, const char *type, const char *subtype) +{ + /* no type == text/plain or text/"*" */ + if (ct==NULL) { + return (!strcasecmp(type, "text") + && (!strcasecmp(subtype, "plain") + || !strcasecmp(subtype, "*"))); + } + + return ((!strcasecmp(ct->type, type) + && (!strcasecmp(ct->subtype, subtype) + || !strcasecmp("*", subtype)))); +} + +void +header_param_list_free(struct _header_param *p) +{ + struct _header_param *n; + + while (p) { + n = p->next; + g_free(p->name); + g_free(p->value); + g_free(p); + p = n; + } +} + +struct _header_content_type * +header_content_type_new(const char *type, const char *subtype) +{ + struct _header_content_type *t = g_malloc(sizeof(*t)); + + t->type = type; + t->subtype = subtype; + t->params = NULL; + t->refcount = 1; + return t; +} + +void +header_content_type_ref(struct _header_content_type *ct) +{ + if (ct) + ct->refcount++; +} + + +void +header_content_type_unref(struct _header_content_type *ct) +{ + if (ct) { + if (ct->refcount <= 1) { + header_param_list_free(ct->params); + g_free(ct->type); + g_free(ct->subtype); + g_free(ct); + } else { + ct->refcount--; + } + } +} + +/* for decoding email addresses, canonically */ +static char * +header_decode_domain(const char **in) +{ + const char *inptr = *in, *start; + int go = TRUE; + GString *domain = g_string_new(""); + + /* domain ref | domain literal */ + header_decode_lwsp(&inptr); + while (go) { + if (*inptr == '[') { /* domain literal */ + g_string_append(domain, "[ "); + inptr++; + header_decode_lwsp(&inptr); + start = inptr; + while (is_dtext(*inptr)) { + g_string_append_c(domain, *inptr); + inptr++; + } + if (*inptr == ']') { + g_string_append(domain, " ]"); + inptr++; + } else { + g_warning("closing ']' not found in domain: %s", *in); + } + } else { + char *a = header_decode_atom(&inptr); + if (a) { + g_string_append(domain, a); + } else { + g_warning("missing atom from domain-ref"); + break; + } + } + header_decode_lwsp(&inptr); + if (*inptr == '.') { /* next sub-domain? */ + g_string_append(domain, " . "); + inptr++; + header_decode_lwsp(&inptr); + } else + go = FALSE; + } + + *in = inptr; + + /* FIXME:L free string header */ + return domain->str; +} + +static char * +header_decode_addrspec(const char **in) +{ + const char *inptr = *in; + char *word; + GString *addr = g_string_new(""); + + header_decode_lwsp(&inptr); + + /* addr-spec */ + word = header_decode_word(&inptr); + if (word) { + g_string_append(addr, word); + header_decode_lwsp(&inptr); + while (*inptr == '.' && word) { + inptr++; + g_string_append_c(addr, '.'); + word = header_decode_word(&inptr); + if (word) { + g_string_append(addr, word); + header_decode_lwsp(&inptr); + } else { + g_warning("Invalid address spec: %s", *in); + } + } + if (*inptr == '@') { + inptr++; + g_string_append_c(addr, '@'); + word = header_decode_domain(&inptr); + if (word) { + g_string_append(addr, word); + } else { + g_warning("Invalid address, missing domain: %s", *in); + } + } else { + g_warning("Invalid addr-spec, missing @: %s", *in); + } + } else { + g_warning("invalid addr-spec, no local part"); + } + + /* FIXME: return null on error? */ + + *in = inptr; + word = addr->str; + g_string_free(addr, FALSE); + return word; +} + +/* + address: + word *('.' word) @ domain | + *(word) '<' [ *('@' domain ) ':' ] word *( '.' word) @ domain | + + 1*word ':' [ word ... etc (mailbox, as above) ] ';' + */ + +/* mailbox: + word *( '.' word ) '@' domain + *(word) '<' [ *('@' domain ) ':' ] word *( '.' word) @ domain + */ + +/* FIXME: what does this return? */ + +static void +header_decode_mailbox(const char **in) +{ + const char *inptr = *in; + char *pre; + int closeme = FALSE; + GString *addr; + + addr = g_string_new(""); + + /* for each address */ + pre = header_decode_word(&inptr); + header_decode_lwsp(&inptr); + if (!(*inptr == '.' || *inptr == '@' || *inptr==',' || *inptr=='\0')) { /* ',' and '\0' required incase it is a simple address, no @ domain part (buggy writer) */ + /* FIXME: rfc 2047 decode each word */ + while (pre) { + /* rfc_decode(pre) */ + pre = header_decode_word(&inptr); + } + header_decode_lwsp(&inptr); + if (*inptr == '<') { + closeme = TRUE; + inptr++; + header_decode_lwsp(&inptr); + if (*inptr == '@') { + while (*inptr == '@') { + inptr++; + header_decode_domain(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == ',') { + inptr++; + header_decode_lwsp(&inptr); + } + } + if (*inptr == ':') { + inptr++; + } else { + g_warning("broken route-address, missing ':': %s", *in); + } + } + pre = header_decode_word(&inptr); + header_decode_lwsp(&inptr); + } else { + g_warning("broken address? %s", *in); + } + } + + if (pre) { + g_string_append(addr, pre); + } else { + g_warning("No local-part for email address: %s", *in); + } + + /* should be at word '.' localpart */ + while (*inptr == '.' && pre) { + inptr++; + pre = header_decode_word(&inptr); + g_string_append_c(addr, '.'); + g_string_append(addr, pre); + header_decode_lwsp(&inptr); + } + + /* now at '@' domain part */ + if (*inptr == '@') { + char *dom; + + inptr++; + g_string_append_c(addr, '@'); + dom = header_decode_domain(&inptr); + g_string_append(addr, dom); + } else { + g_warning("invalid address, no '@' domain part at %c: %s", *inptr, *in); + } + + if (closeme) { + header_decode_lwsp(&inptr); + if (*inptr == '>') { + inptr++; + } else { + g_warning("invalid route address, no closing '>': %s", *in); + } + } + + *in = inptr; + + d(printf("got mailbox: %s\n", addr->str)); +} + +/* FIXME: what does this return? */ +static void +header_decode_address(const char **in) +{ + const char *inptr = *in; + char *pre; + GString *group = g_string_new(""); + + /* pre-scan, trying to work out format, discard results */ + header_decode_lwsp(&inptr); + while ( (pre = header_decode_word(&inptr)) ) { + g_string_append(group, pre); + g_string_append(group, " "); + g_free(pre); + } + header_decode_lwsp(&inptr); + if (*inptr == ':') { + d(printf("group detected: %s\n", group->str)); + /* that was a group spec, scan mailbox's */ + inptr++; + /* FIXME: check rfc 2047 encodings of words, here or above in the loop */ + header_decode_lwsp(&inptr); + if (*inptr != ';') { + int go = TRUE; + do { + header_decode_mailbox(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == ',') + inptr++; + else + go = FALSE; + } while (go); + if (*inptr == ';') { + inptr++; + } else { + g_warning("Invalid group spec, missing closing ';': %s", *in); + } + } else { + inptr++; + } + *in = inptr; +/* } else if (*inptr == '.' || *inptr == '<' || *inptr == '@') {*/ + } else { + /* back-track, and rescan. not worth the code duplication to do this faster */ + /* this will detect invalid input */ + header_decode_mailbox(in); + }/* else { + g_warning("Cannot scan address at '%c': %s", *inptr, *in); + }*/ + + /* FIXME: store gropu somewhere */ + g_string_free(group, TRUE); +} + +char * +header_msgid_decode(const char *in) +{ + const char *inptr = in; + char *msgid = NULL; + + d(printf("decoding Message-ID: '%s'\n", in)); + + if (in == NULL) + return NULL; + + header_decode_lwsp(&inptr); + if (*inptr == '<') { + inptr++; + header_decode_lwsp(&inptr); + msgid = header_decode_addrspec(&inptr); + if (msgid) { + header_decode_lwsp(&inptr); + if (*inptr == '>') { + inptr++; + } else { + g_warning("Missing closing '>' on message id: %s", in); + } + } else { + g_warning("Cannot find message id in: %s", in); + } + } else { + g_warning("missing opening '<' on message id: %s", in); + } + + if (msgid) { + d(printf("Got message id: %s\n", msgid)); + } + return msgid; +} + +void +header_to_decode(const char *in) +{ + const char *inptr = in, *last; + + d(printf("decoding To: '%s'\n", in)); + + if (in == NULL) + return NULL; + + do { + last = inptr; + header_decode_address(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == ',') + inptr++; + else + break; + } while (inptr != last); + + if (*inptr) { + g_warning("Invalid input detected at %c (%d): %s\n or at: %s", *inptr, inptr-in, in, inptr); + } + + if (inptr == last) { + g_warning("detected invalid input loop at : %s", last); + } +} + +void +header_mime_decode(const char *in) +{ + const char *inptr = in; + int major=-1, minor=-1; + + d(printf("decoding MIME-Version: '%s'\n", in)); + + if (in == NULL) + return NULL; + + header_decode_lwsp(&inptr); + if (isdigit(*inptr)) { + major = header_decode_int(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == '.') { + inptr++; + header_decode_lwsp(&inptr); + if (isdigit(*inptr)) + minor = header_decode_int(&inptr); + } + } + + d(printf("major = %d, minor = %d\n", major, minor)); +} + +struct _header_param * +header_param_list_decode(const char **in) +{ + const char *inptr = *in; + struct _header_param *head = NULL, *tail = NULL; + + header_decode_lwsp(&inptr); + while (*inptr == ';') { + char *param, *value; + struct _header_param *p; + + inptr++; + /* invalid format? */ + if (header_decode_param(&inptr, ¶m, &value) != 0) + break; + + p = g_malloc(sizeof(*p)); + p->name = param; + p->value = value; + p->next = NULL; + if (head == NULL) + head = p; + if (tail) + tail->next = p; + tail = p; + header_decode_lwsp(&inptr); + } + *in = inptr; + return head; +} + +struct _header_content_type * +header_content_type_decode(const char *in) +{ + const char *inptr = in; + char *type, *subtype = NULL; + struct _header_content_type *t = NULL; + + if (in==NULL) + return NULL; + + type = header_decode_token(&inptr); + header_decode_lwsp(&inptr); + if (type) { + if (*inptr == '/') { + inptr++; + subtype = header_decode_token(&inptr); + } + if (subtype == NULL && (!strcasecmp(type, "text"))) { + g_warning("text type with no subtype, resorting to text/plain: %s", in); + subtype = g_strdup("plain"); + } + if (subtype == NULL) { + g_warning("MIME type with no subtype: %s", in); + } + + t = header_content_type_new(type, subtype); + t->params = header_param_list_decode(&inptr); + } else { + g_free(type); + d(printf("cannot find MIME type in header (2) '%s'", in)); + } + return t; +} + +void +header_content_type_dump(struct _header_content_type *ct) +{ + struct _header_param *p; + + printf("Content-Type: "); + if (ct==NULL) { + printf("<NULL>\n"); + return; + } + printf("%s / %s", ct->type, ct->subtype); + p = ct->params; + if (p) { + while (p) { + printf(";\n\t%s=\"%s\"", p->name, p->value); + p = p->next; + } + } + printf("\n"); +} + +char * +header_content_encoding_decode(const char *in) +{ + if (in) + return header_decode_token(&in); + return NULL; +} + +CamelMimeDisposition *header_disposition_decode(const char *in) +{ + CamelMimeDisposition *d = NULL; + const char *inptr = in; + + if (in == NULL) + return NULL; + + d = g_malloc(sizeof(*d)); + d->refcount = 1; + d->disposition = header_decode_token(&inptr); + if (d->disposition == NULL) + g_warning("Empty disposition type"); + d->params = header_param_list_decode(&inptr); + return d; +} + +void header_disposition_ref(CamelMimeDisposition *d) +{ + if (d) + d->refcount++; +} +void header_disposition_unref(CamelMimeDisposition *d) +{ + if (d) { + if (d->refcount<=1) { + header_param_list_free(d->params); + g_free(d->disposition); + g_free(d); + } else { + d->refcount--; + } + } +} + +/* hrm, is there a library for this shit? */ +static struct { + char *name; + int offset; +} tz_offsets [] = { + { "UT", 0 }, + { "GMT", 0 }, + { "EST", -500 }, /* these are all US timezones. bloody yanks */ + { "EDT", -400 }, + { "CST", -600 }, + { "CDT", -500 }, + { "MST", -700 }, + { "MDT", -600 }, + { "PST", -800 }, + { "PDT", -700 }, + { "Z", 0 }, + { "A", -100 }, + { "M", -1200 }, + { "N", 100 }, + { "Y", 1200 }, +}; + +static char *tz_months [] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nove", "Dec" +}; + +char * +header_format_date(time_t time, int offset) +{ + struct tm tm; + + d(printf("offset = %d\n", offset)); + + d(printf("converting date %s", ctime(&time))); + + time += ((offset / 100) * (60*60)) + (offset % 100)*60; + + d(printf("converting date %s", ctime(&time))); + + memcpy(&tm, gmtime(&time), sizeof(tm)); + + return g_strdup_printf("%02d %s %04d %02d:%02d:%02d %c%04d", + tm.tm_mday, tz_months[tm.tm_mon], + tm.tm_year + 1900, + tm.tm_hour, tm.tm_min, tm.tm_sec, + offset>=0?'+':'-', + offset); +} + +/* convert a date to time_t representation */ +/* this is an awful mess oh well */ +time_t +header_decode_date(const char *in, int *saveoffset) +{ + const char *inptr = in; + char *monthname; + int year, offset = 0; + struct tm tm; + int i; + time_t t; + + if (in == NULL) { + if (*saveoffset) + *saveoffset = 0; + return 0; + } + + d(printf("\ndecoding date '%s'\n", inptr)); + + memset(&tm, 0, sizeof(tm)); + + header_decode_lwsp(&inptr); + if (!isdigit(*inptr)) { + char *day = header_decode_token(&inptr); + /* we dont really care about the day, its only for display */ + if (day) { + d(printf("got day: %s\n", day)); + g_free(day); + header_decode_lwsp(&inptr); + if (*inptr == ',') + inptr++; + else + g_warning("day not followed by ','"); + } + } + tm.tm_mday = header_decode_int(&inptr); + monthname = header_decode_token(&inptr); + if (monthname) { + for (i=0;i<sizeof(tz_months)/sizeof(tz_months[0]);i++) { + if (!strcasecmp(tz_months[i], monthname)) { + tm.tm_mon = i; + break; + } + } + g_free(monthname); + } + year = header_decode_int(&inptr); + if (year<100) { + tm.tm_year = year; + } else { + tm.tm_year = year-1900; + } + /* get the time ... yurck */ + tm.tm_hour = header_decode_int(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == ':') + inptr++; + tm.tm_min = header_decode_int(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == ':') + inptr++; + tm.tm_sec = header_decode_int(&inptr); + header_decode_lwsp(&inptr); + if (*inptr == '+' + || *inptr == '-') { + offset = (*inptr++)=='-'?-1:1; + offset = offset * header_decode_int(&inptr); + d(printf("abs signed offset = %d\n", offset)); + } else if (isdigit(*inptr)) { + offset = header_decode_int(&inptr); + d(printf("abs offset = %d\n", offset)); + } else { + char *tz = header_decode_token(&inptr); + + if (tz) { + for (i=0;i<sizeof(tz_offsets)/sizeof(tz_offsets[0]);i++) { + if (!strcasecmp(tz_offsets[i].name, tz)) { + offset = tz_offsets[i].offset; + break; + } + } + g_free(tz); + } + /* some broken mailers seem to put in things like GMT+1030 instead of just +1030 */ + header_decode_lwsp(&inptr); + if (*inptr == '+' || *inptr == '-') { + int sign = (*inptr++)=='-'?-1:1; + offset = offset + (header_decode_int(&inptr)*sign); + } + d(printf("named offset = %d\n", offset)); + } + + /* t -= ( (offset/100) * 60*60) + (offset % 100)*60 + timezone;*/ + + t = mktime(&tm) - timezone; + + /* t is now GMT of the time we want, but not offset by the timezone ... */ + + d(printf(" gmt normalized? = %s\n", ctime(&t))); + + /* this should convert the time to the GMT equiv time */ + t -= ( (offset/100) * 60*60) + (offset % 100)*60; + + d(printf(" gmt normalized for timezone? = %s\n", ctime(&t))); + + d({ + char *tmp; + tmp = header_format_date(t, offset); + printf(" encoded again: %s\n", tmp); + g_free(tmp); + }); + + if (saveoffset) + *saveoffset = offset; + + return t; +} + +/* extra rfc checks */ +#define CHECKS + +#ifdef CHECKS +static void +check_header(struct _header_raw *h) +{ + unsigned char *p; + + p = h->value; + while (*p) { + if (!isascii(*p)) { + g_warning("Appending header violates rfc: %s: %s", h->name, h->value); + return; + } + p++; + } +} +#endif + +void +header_raw_append_parse(struct _header_raw **list, const char *header, int offset) +{ + register const char *in; + int fieldlen; + char *name; + + in = header; + while (is_fieldname(*in)) + in++; + fieldlen = in-header; + while (is_lwsp(*in)) + in++; + if (fieldlen == 0 || *in != ':') { + printf("Invalid header line: '%s'\n", header); + return; + } + in++; + name = alloca(fieldlen+1); + memcpy(name, header, fieldlen); + name[fieldlen] = 0; + + header_raw_append(list, name, in, offset); +} + +void +header_raw_append(struct _header_raw **list, const char *name, const char *value, int offset) +{ + struct _header_raw *l, *n; + + d(printf("Header: %s: %s\n", name, value)); + + n = g_malloc(sizeof(*n)); + n->next = NULL; + n->name = g_strdup(name); + n->value = g_strdup(value); + n->offset = offset; +#ifdef CHECKS + check_header(n); +#endif + l = (struct _header_raw *)list; + while (l->next) { + l = l->next; + } + l->next = n; + + /* debug */ +#if 0 + if (!strcasecmp(name, "To")) { + printf("- Decoding To\n"); + header_to_decode(value); + } else if (!strcasecmp(name, "Content-type")) { + printf("- Decoding content-type\n"); + header_content_type_dump(header_content_type_decode(value)); + } else if (!strcasecmp(name, "MIME-Version")) { + printf("- Decoding mime version\n"); + header_mime_decode(value); + } +#endif +} + +static struct _header_raw * +header_raw_find_node(struct _header_raw **list, const char *name) +{ + struct _header_raw *l; + + l = *list; + while (l) { + if (!strcasecmp(l->name, name)) + break; + l = l->next; + } + return l; +} + +const char * +header_raw_find(struct _header_raw **list, const char *name, int *offset) +{ + struct _header_raw *l; + + l = header_raw_find_node(list, name); + if (l) { + if (offset) + *offset = l->offset; + return l->value; + } else + return NULL; +} + +static void +header_raw_free(struct _header_raw *l) +{ + g_free(l->name); + g_free(l->value); + g_free(l); +} + +void +header_raw_remove(struct _header_raw **list, const char *name) +{ + struct _header_raw *l, *p; + + /* the next pointer is at the head of the structure, so this is safe */ + p = (struct _header_raw *)list; + l = *list; + while (l) { + if (!strcasecmp(l->name, name)) { + p->next = l->next; + header_raw_free(l); + l = p->next; + } else { + p = l; + l = l->next; + } + } +} + +void +header_raw_replace(struct _header_raw **list, const char *name, const char *value, int offset) +{ + header_raw_remove(list, name); + header_raw_append(list, name, value, offset); +} + +void +header_raw_clear(struct _header_raw **list) +{ + struct _header_raw *l, *n; + l = *list; + while (l) { + n = l->next; + header_raw_free(l); + l = n; + } + *list = NULL; +} + + + +#ifdef BUILD_TABLE + +/* for debugging tests */ +/* should also have some regression tests somewhere */ + +void run_test(void) +{ + char *to = "gnome hacker dudes: license-discuss@opensource.org, + \"Richard M. Stallman\" <rms@gnu.org>, + Barry Chester <barry_che@antdiv.gov.au>, + Michael Zucchi <zucchi.michael(this (is a nested) comment)@zedzone.mmc.com.au>, + Miguel de Icaza <miguel@gnome.org>;, + zucchi@zedzone.mmc.com.au, \"Foo bar\" <zed@zedzone>, + <frob@frobzone>"; + + header_to_decode(to); + + header_mime_decode("1.0"); + header_mime_decode("1.3 (produced by metasend V1.0)"); + header_mime_decode("(produced by metasend V1.0) 5.2"); + header_mime_decode("7(produced by metasend 1.0) . (produced by helix/send/1.0) 9 . 5"); + header_mime_decode("3."); + header_mime_decode("."); + header_mime_decode(".5"); + header_mime_decode("c.d"); + header_mime_decode(""); + + header_msgid_decode(" <\"L3x2i1.0.Nm5.Xd-Wu\"@lists.redhat.com>"); + header_msgid_decode("<200001180446.PAA02065@beaker.htb.com.au>"); + +} + +#endif /* BUILD_TABLE */ diff --git a/camel/camel-mime-utils.h b/camel/camel-mime-utils.h new file mode 100644 index 0000000000..931e479a99 --- /dev/null +++ b/camel/camel-mime-utils.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _CAMEL_MIME_UTILS_H +#define _CAMEL_MIME_UTILS_H + +#include <time.h> + +struct _header_param { + struct _header_param *next; + char *name; + char *value; +}; + +/* describes a content-type */ +struct _header_content_type { + char *type; + char *subtype; + struct _header_param *params; + unsigned int refcount; +}; + +/* a raw rfc822 header */ +/* the value MUST be US-ASCII */ +struct _header_raw { + struct _header_raw *next; + char *name; + char *value; + int offset; /* in file, if known */ +}; + +typedef struct _CamelMimeDisposition { + char *disposition; + struct _header_param *params; + unsigned int refcount; +} CamelMimeDisposition; + +struct _header_content_type *header_content_type_new(const char *type, const char *subtype); +struct _header_content_type *header_content_type_decode(const char *in); +void header_content_type_unref(struct _header_content_type *ct); +void header_content_type_ref(struct _header_content_type *ct); +const char *header_content_type_param(struct _header_content_type *t, const char *name); +void header_content_type_set_param(struct _header_content_type *t, const char *name, const char *value); +int header_content_type_is(struct _header_content_type *ct, const char *type, const char *subtype); + +char *header_param(struct _header_param *p, char *name); +struct _header_param *header_set_param(struct _header_param **l, const char *name, const char *value); + +CamelMimeDisposition *header_disposition_decode(const char *in); +void header_disposition_ref(CamelMimeDisposition *); +void header_disposition_unref(CamelMimeDisposition *); + +/* decode the contents of a content-encoding header */ +char *header_content_encoding_decode(const char *in); + +/* working with lists of headers */ +void header_raw_append(struct _header_raw **list, const char *name, const char *value, int offset); +void header_raw_append_parse(struct _header_raw **list, const char *header, int offset); +const char *header_raw_find(struct _header_raw **list, const char *name, int *ofset); +void header_raw_replace(struct _header_raw **list, const char *name, const char *value, int offset); +void header_raw_remove(struct _header_raw **list, const char *name); +void header_raw_clear(struct _header_raw **list); + +/* raw header parsing functions */ +char *header_decode_token(const char **in); + +/* decode a string type, like a subject line */ +char *header_decode_string(const char *in); + +/* decode an email date field into a GMT time, + optional offset */ +time_t header_decode_date(const char *in, int *saveoffset); +char *header_format_date(time_t time, int offset); + +/* decode a message id */ +char *header_msgid_decode(const char *in); + +/* do incremental base64/quoted-printable (de/en)coding */ +int base64_decode_step(unsigned char *in, int len, unsigned char *out, int *state, unsigned int *save); + +int base64_encode_step(unsigned char *in, int len, unsigned char *out, int *state, int *save); +int base64_encode_close(unsigned char *in, int inlen, unsigned char *out, int *state, int *save); + +int quoted_decode_step(unsigned char *in, int len, unsigned char *out, int *savestate, int *saveme); + +int quoted_encode_step(unsigned char *in, int len, unsigned char *out, int *state, int *save); +int quoted_encode_close(unsigned char *in, int len, unsigned char *out, int *state, int *save); + +#endif /* ! _CAMEL_MIME_UTILS_H */ diff --git a/camel/camel-stream-b64.c b/camel/camel-stream-b64.c deleted file mode 100644 index f7f0eeecfd..0000000000 --- a/camel/camel-stream-b64.c +++ /dev/null @@ -1,581 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ - - -/* - * - * Author : - * Bertrand Guiheneuf <bertrand@helixcode.com> - * - * Copyright 1999, 2000 Helix Code, Inc. (http://www.helixcode.com) - * - * 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 <config.h> -#include "camel-stream-b64.h" - -#define BSIZE 512 - - - -static CamelStreamClass *parent_class = NULL; - -static guchar char_to_six_bits [256] = { - 128, 128, 128, 128, 128, 128, 128, 128, /* 0 .. 7 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 8 .. 15 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 16 .. 23 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 24 .. 31 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 32 .. 39 */ - 128, 128, 128, 62, 128, 128, 128, 63, /* 40 .. 47 */ - 52, 53, 54, 55, 56, 57, 58, 59, /* 48 .. 55 */ - 60, 61, 128, 128, 128, 64, 128, 128, /* 56 .. 63 */ - 128, 0, 1, 2, 3, 4, 5, 6, /* 64 .. 71 */ - 7, 8, 9, 10, 11, 12, 13, 14, /* 72 .. 79 */ - 15, 16, 17, 18, 19, 20, 21, 22, /* 80 .. 87 */ - 23, 24, 25, 128, 128, 128, 128, 128, /* 88 .. 95 */ - 128, 26, 27, 28, 29, 30, 31, 32, /* 96 .. 103 */ - 33, 34, 35, 36, 37, 38, 39, 40, /* 104 .. 111 */ - 41, 42, 43, 44, 45, 46, 47, 48, /* 112 .. 119 */ - 49, 50, 51, 128, 128, 128, 128, 128, /* 120 .. 127 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 128 .. 135 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 136 .. 143 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 144 .. 151 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 152 .. 159 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 160 .. 167 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 168 .. 175 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 176 .. 183 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 184 .. 191 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 192 .. 199 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 200 .. 207 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 208 .. 215 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 216 .. 223 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 224 .. 231 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 232 .. 239 */ - 128, 128, 128, 128, 128, 128, 128, 128, /* 240 .. 247 */ - 128, 128, 128, 128, 128, 128, 128, 128 /* 248 .. 255 */ -}; - - -static gchar six_bits_to_char[65] = -"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - -/* Returns the class for a CamelStreamB64 */ -#define CSB64_CLASS(so) CAMEL_STREAM_B64_CLASS (GTK_OBJECT(so)->klass) - -static void my_init_with_input_stream (CamelStreamB64 *stream_b64, - CamelStream *input_stream); - -static gint my_read (CamelStream *stream, - gchar *buffer, - gint n); - -static void my_reset (CamelStream *stream); - -static gint my_read_decode (CamelStream *stream, - gchar *buffer, - gint n); -static gint my_read_encode (CamelStream *stream, - gchar *buffer, - gint n); -static gboolean my_eos (CamelStream *stream); - -static void -camel_stream_b64_class_init (CamelStreamB64Class *camel_stream_b64_class) -{ - CamelStreamClass *camel_stream_class = CAMEL_STREAM_CLASS (camel_stream_b64_class); - - - parent_class = gtk_type_class (camel_stream_get_type ()); - - /* virtual method definition */ - camel_stream_b64_class->init_with_input_stream = my_init_with_input_stream; - - - /* virtual method overload */ - camel_stream_class->read = my_read; - camel_stream_class->eos = my_eos; - camel_stream_class->reset = my_reset; - - /* signal definition */ - -} - -GtkType -camel_stream_b64_get_type (void) -{ - static GtkType camel_stream_b64_type = 0; - - if (!camel_stream_b64_type) { - GtkTypeInfo camel_stream_b64_info = - { - "CamelStreamB64", - sizeof (CamelStreamB64), - sizeof (CamelStreamB64Class), - (GtkClassInitFunc) camel_stream_b64_class_init, - (GtkObjectInitFunc) NULL, - /* reserved_1 */ NULL, - /* reserved_2 */ NULL, - (GtkClassInitFunc) NULL, - }; - - camel_stream_b64_type = gtk_type_unique (camel_stream_get_type (), &camel_stream_b64_info); - } - - return camel_stream_b64_type; -} - - -static void -my_reemit_available_signal (CamelStream *parent_stream, gpointer user_data) -{ - gtk_signal_emit_by_name (GTK_OBJECT (user_data), "data_available"); -} - -static void -my_init_with_input_stream (CamelStreamB64 *stream_b64, - CamelStream *input_stream) -{ - g_assert (stream_b64); - g_assert (input_stream); - - - - /* by default, the stream is in decode mode */ - stream_b64->mode = CAMEL_STREAM_B64_DECODER; - - stream_b64->eos = FALSE; - stream_b64->status.decode_status.keep = 0; - stream_b64->status.decode_status.state = 0; - - stream_b64->input_stream = input_stream; - - gtk_object_ref (GTK_OBJECT (input_stream)); - - /* - * connect to the parent stream "data_available" - * stream so that we can reemit the signal on the - * seekable substream in case some data would - * be available for us - */ - gtk_signal_connect (GTK_OBJECT (input_stream), - "data_available", - my_reemit_available_signal, - stream_b64); - - - /* bootstrapping signal */ - gtk_signal_emit_by_name (GTK_OBJECT (stream_b64), "data_available"); - - -} - - - -CamelStream * -camel_stream_b64_new_with_input_stream (CamelStream *input_stream) -{ - CamelStreamB64 *stream_b64; - - stream_b64 = gtk_type_new (camel_stream_b64_get_type ()); - CSB64_CLASS (stream_b64)->init_with_input_stream (stream_b64, input_stream); - - return CAMEL_STREAM (stream_b64); -} - - - - -void -camel_stream_b64_set_mode (CamelStreamB64 *stream_b64, - CamelStreamB64Mode mode) -{ - g_assert (stream_b64); - - stream_b64->mode = mode; - - if (mode == CAMEL_STREAM_B64_DECODER) { - stream_b64->status.decode_status.keep = 0; - stream_b64->status.decode_status.state = 0; - } else { - stream_b64->status.encode_status.keep = 0; - stream_b64->status.encode_status.state = 0; - stream_b64->status.encode_status.end_state = 0; - } - -} - - - - -static gint -my_read (CamelStream *stream, - gchar *buffer, - gint n) -{ - CamelStreamB64 *stream_b64 = CAMEL_STREAM_B64 (stream); - - g_assert (stream); - - if (stream_b64->mode == CAMEL_STREAM_B64_DECODER) - return my_read_decode (stream, buffer, n); - else - return my_read_encode (stream, buffer, n); -} - - - -static gint -my_read_decode (CamelStream *stream, - gchar *buffer, - gint n) -{ - CamelStreamB64 *stream_b64 = CAMEL_STREAM_B64 (stream); - CamelStream64DecodeStatus *status; - CamelStream *input_stream; - guchar six_bits_value; - gint nb_read_in_input; - guchar c; - gint j = 0; - - g_assert (stream); - input_stream = stream_b64->input_stream; - - g_assert (input_stream); - status = &(stream_b64->status.decode_status); - - - nb_read_in_input = camel_stream_read (input_stream, &c, 1); - - while ((nb_read_in_input >0 ) && (j<n)) { - - six_bits_value = char_to_six_bits[c]; - - /* if we encounter an '=' we can assume the end of the stream - has been found */ - if (six_bits_value == 64) { - stream_b64->eos = TRUE; - status->keep = 0; - - break; - } - - /* test if we must ignore the character */ - if (six_bits_value != 128) { - six_bits_value = six_bits_value & 0x3f; - - switch (status->state){ - case 0: - status->keep = six_bits_value << 2; - - break; - case 1: - buffer [j++] = status->keep | (six_bits_value >> 4); - status->keep = (six_bits_value & 0xf) << 4; - break; - case 2: - buffer [j++] = status->keep | (six_bits_value >> 2); - status->keep = (six_bits_value & 0x3) << 6; - break; - case 3: - buffer [j++] = status->keep | six_bits_value; - status->keep = 0; - break; - } - - status->state = (status->state + 1) % 4; - - } - - if (j<n) nb_read_in_input = camel_stream_read (input_stream, &c, 1); - - } - - if ((nb_read_in_input == 0) && (camel_stream_eos (input_stream))) - stream_b64->eos = TRUE; - - return j; - -} - - -static gint -my_read_encode (CamelStream *stream, - gchar *buffer, - gint n) -{ - CamelStreamB64 *stream_b64 = CAMEL_STREAM_B64 (stream); - CamelStream64EncodeStatus *status; - CamelStream *input_stream; - gint nb_read_in_input = 0; - guchar c; - gint j = 0; - gboolean end_of_read = FALSE; - - g_assert (stream); - input_stream = stream_b64->input_stream; - - g_assert (input_stream); - - /* I don't know why the caller would want to - read a zero length buffer but ... */ - if (n == 0) - return 0; - - - status = &(stream_b64->status.encode_status); - - - if (status->end_state == 0) { - /* we are not at the end of the input steam, - process the data normally */ - - while ((j<n) && !end_of_read) { - - /* check if we must break the encoded line */ - if (status->line_length == 76) { - buffer [j++] = '\n'; - status->line_length = 0; - break; - } - - /* - * because we encode four characters for - * 3 bytes, the last char does not need any - * read to write in the stream - */ - if (status->state == 3) { - buffer [j++] = six_bits_to_char [status->keep]; - status->state = 0; - status->keep = 0; - status->line_length++; - break; - } - - /* - * in all the other phases of the stream - * writing, we need to read a byte from the - * input stream - */ - nb_read_in_input = camel_stream_read (input_stream, &c, 1); - - if (nb_read_in_input > 0) { - switch (status->state){ - - case 0: - buffer [j++] = six_bits_to_char [(c >> 2) & 0x3f]; - status->keep = (c & 0x3 ) << 4; - break; - - case 1: - buffer [j++] = six_bits_to_char [status->keep | (c >> 4)]; - status->keep = (c & 0x0f ) << 2; - break; - - case 2: - buffer [j++] = six_bits_to_char [status->keep | (c >> 6)] ; - status->keep = (c & 0x3f ); - break; - - } - - status->state = (status->state + 1) % 4; - status->line_length++; - } else - end_of_read = TRUE; - - - if (camel_stream_eos (input_stream)) - status->end_state = 1; - - } - } - - /* - * now comes the real annoying part. Because some clients - * expect the b64 encoded sequence length to be multiple of 4, - * we must pad the end with '='. - * This is trivial when we write to stream as much as we want - * but this is not the case when we are limited in the number - * of chars we can write to the output stream. The consequence - * of this is that we must keep the state of the writing - * so that we can resume the next time this routine is called. - */ - - if ( status->end_state != 0) { - - /* - * we are at the end of the input stream - * we must pad the output with '='. - */ - while ((j<n) && (status->end_state != 6)) { - - if (status->end_state == 5) { - - status->end_state = 6; - buffer [j++] = '\n'; - stream_b64->eos = TRUE; - - } else { - - switch (status->state) { - - /* - * depending on state of the decoder, we need to - * write different things. - */ - case 0: - /* - * everyting has been written already and the - * output length is already a multiple of 3 - * so that we have nothing to do. - */ - status->end_state = 5; - break; - - case 1: - /* - * we have something in keep - * and two '=' we must write - */ - switch (status->end_state) { - case 1: - buffer [j++] = six_bits_to_char [status->keep] ; - status->end_state++; - break; - case 2: - buffer [j++] = '='; - status->end_state++; - break; - case 3: - buffer [j++] = '='; - status->end_state = 5; - break; - } - - - break; - - - case 2: - /* - * we have something in keep - * and one '=' we must write - */ - switch (status->end_state) { - case 1: - buffer [j++] = six_bits_to_char [status->keep]; - status->end_state++; - break; - case 2: - buffer [j++] = '='; - status->end_state = 5; - break; - } - - break; - - case 3: - /* - * we have something in keep we must write - */ - switch (status->end_state) { - case 1: - buffer [j++] = six_bits_to_char [status->keep]; - status->end_state++; - break; - case 2: - buffer [j++] = '='; - status->end_state = 5; - break; - } - - break; - } - - - } - } - - } - - return j; -} - - - - - -static gboolean -my_eos (CamelStream *stream) -{ - CamelStreamB64 *stream_b64 = CAMEL_STREAM_B64 (stream); - - g_assert (stream); - g_assert (stream_b64->input_stream); - - return (stream_b64->eos); -} - - - - - -static void -my_reset (CamelStream *stream) -{ - CamelStreamB64 *stream_b64 = CAMEL_STREAM_B64 (stream); - - g_assert (stream); - g_assert (stream_b64->input_stream); - - stream_b64->status.decode_status.keep = 0; - stream_b64->status.decode_status.state = 0; - - stream_b64->eos = FALSE; - - camel_stream_reset (stream_b64->input_stream); -} - - - -void -camel_stream_b64_write_to_stream (CamelStreamB64 *stream_b64, - CamelStream *output_stream) -{ - gchar tmp_buf[4096]; - gint nb_read; - gint nb_written; - - /* - * default implementation that uses the input - * stream and stream it in a blocking way - * to an output stream. - */ - g_assert (output_stream); - g_assert (stream_b64); - - while (!camel_stream_eos (CAMEL_STREAM (stream_b64))) { - nb_read = camel_stream_read (CAMEL_STREAM (stream_b64), tmp_buf, 4096); - nb_written = 0; - - while (nb_written < nb_read) - nb_written += camel_stream_write (output_stream, tmp_buf + nb_written, nb_read - nb_written); - } - -} - - - - diff --git a/camel/camel-stream-b64.h b/camel/camel-stream-b64.h deleted file mode 100644 index 73bf4f09a4..0000000000 --- a/camel/camel-stream-b64.h +++ /dev/null @@ -1,135 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ - -/* - * - * Author : - * Bertrand Guiheneuf <bertrand@helixcode.com> - * - * Copyright 1999, 2000 Helix Code, Inc. (http://www.helixcode.com) - * - * 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 CAMEL_STREAM_B64_H -#define CAMEL_STREAM_B64_H 1 - - -#ifdef __cplusplus -extern "C" { -#pragma } -#endif /* __cplusplus }*/ - -#include <gtk/gtk.h> -#include "camel-stream.h" - - -#define CAMEL_STREAM_B64_TYPE (camel_stream_b64_get_type ()) -#define CAMEL_STREAM_B64(obj) (GTK_CHECK_CAST((obj), CAMEL_STREAM_B64_TYPE, CamelStreamB64)) -#define CAMEL_STREAM_B64_CLASS(k) (GTK_CHECK_CLASS_CAST ((k), CAMEL_STREAM_B64_TYPE, CamelStreamB64Class)) -#define CAMEL_IS_STREAM_B64(o) (GTK_CHECK_TYPE((o), CAMEL_STREAM_B64_TYPE)) - - -typedef enum { - - CAMEL_STREAM_B64_DECODER, - CAMEL_STREAM_B64_ENCODER - -} CamelStreamB64Mode; - - -/* private type */ -typedef struct { - - guchar state; - guchar keep; - -} CamelStream64DecodeStatus; - -typedef struct { - - guchar state; - guchar keep; - guchar end_state; - guchar line_length; - -} CamelStream64EncodeStatus; - - -typedef union { - CamelStream64DecodeStatus decode_status; - CamelStream64EncodeStatus encode_status; -} CamelStream64Status; - -typedef struct -{ - CamelStream parent_object; - - /* -- all these fields are private -- */ - - CamelStream *input_stream; /* the stream we get the data from before co/de-coding them */ - CamelStreamB64Mode mode; /* the stream code or decode in B64 depending on that flag */ - gboolean eos; - - /* decoding status */ - CamelStream64Status status; - -} CamelStreamB64; - - - -typedef struct { - CamelStreamClass parent_class; - - /* Virtual methods */ - void (*init_with_input_stream) (CamelStreamB64 *stream_b64, CamelStream *input_stream); - -} CamelStreamB64Class; - - - - - - -/* Standard Gtk function */ -GtkType camel_stream_b64_get_type (void); - - - - -/* public methods */ -CamelStream * camel_stream_b64_new_with_input_stream (CamelStream *input_stream); - -void camel_stream_b64_set_mode (CamelStreamB64 *stream_b64, - CamelStreamB64Mode mode); - -/* utility function that writes the whole de/en-coded - stream to an output stream */ -void camel_stream_b64_write_to_stream (CamelStreamB64 *stream, - CamelStream *output_stream); - - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* CAMEL_STREAM_B64_H */ - - - - - - diff --git a/camel/camel-stream-filter.c b/camel/camel-stream-filter.c new file mode 100644 index 0000000000..9d6c7d11b0 --- /dev/null +++ b/camel/camel-stream-filter.c @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "camel-stream-filter.h" + +struct _filter { + struct _filter *next; + int id; + CamelMimeFilter *filter; +}; + +struct _CamelStreamFilterPrivate { + struct _filter *filters; + int filterid; /* next filter id */ + + char *realbuffer; /* buffer - READ_PAD */ + char *buffer; /* READ_SIZE bytes */ + + char *filtered; /* the filtered data */ + size_t filteredlen; +}; + +#define READ_PAD (64) /* bytes padded before buffer */ +#define READ_SIZE (4096) + +#define _PRIVATE(o) (((CamelStreamFilter *)(o))->priv) + +static void camel_stream_filter_class_init (CamelStreamFilterClass *klass); +static void camel_stream_filter_init (CamelStreamFilter *obj); + +static gint do_read (CamelStream *stream, gchar *buffer, gint n); +static gint do_write (CamelStream *stream, const gchar *buffer, gint n); +static void do_flush (CamelStream *stream); +static gboolean do_available (CamelStream *stream); +static gboolean do_eos (CamelStream *stream); +static void do_close (CamelStream *stream); +static void do_reset (CamelStream *stream); + +static CamelStreamClass *camel_stream_filter_parent; + +enum SIGNALS { + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +guint +camel_stream_filter_get_type (void) +{ + static guint type = 0; + + if (!type) { + GtkTypeInfo type_info = { + "CamelStreamFilter", + sizeof (CamelStreamFilter), + sizeof (CamelStreamFilterClass), + (GtkClassInitFunc) camel_stream_filter_class_init, + (GtkObjectInitFunc) camel_stream_filter_init, + (GtkArgSetFunc) NULL, + (GtkArgGetFunc) NULL + }; + + type = gtk_type_unique (camel_stream_get_type (), &type_info); + } + + return type; +} + +static void +finalise(GtkObject *o) +{ + CamelStreamFilter *filter = (CamelStreamFilter *)o; + struct _CamelStreamFilterPrivate *p = _PRIVATE(filter); + struct _filter *fn, *f; + + f = p->filters; + while (f) { + fn = f->next; + gtk_object_unref((GtkObject *)f->filter); + g_free(f); + f = fn; + } + g_free(p->realbuffer); + g_free(p); +} + + +static void +camel_stream_filter_class_init (CamelStreamFilterClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + CamelStreamClass *camel_stream_class = (CamelStreamClass *) klass; + + camel_stream_filter_parent = gtk_type_class (camel_stream_get_type ()); + + object_class->finalize = finalise; + + camel_stream_class->read = do_read; + camel_stream_class->write = do_write; + camel_stream_class->flush = do_flush; + camel_stream_class->available = do_available; + camel_stream_class->eos = do_eos; + camel_stream_class->close = do_close; + camel_stream_class->reset = do_reset; + + gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL); +} + +static void +camel_stream_filter_init (CamelStreamFilter *obj) +{ + struct _CamelStreamFilterPrivate *p; + + _PRIVATE(obj) = p = g_malloc0(sizeof(*p)); + p->realbuffer = g_malloc(READ_SIZE + READ_PAD); + p->buffer = p->realbuffer + READ_PAD; +} + +/** + * camel_stream_filter_new: + * + * Create a new CamelStreamFilter object. + * + * Return value: A new CamelStreamFilter object. + **/ +CamelStreamFilter * +camel_stream_filter_new_with_stream(CamelStream *stream) +{ + CamelStreamFilter *new = CAMEL_STREAM_FILTER ( gtk_type_new (camel_stream_filter_get_type ())); + + new->source = stream; + return new; +} + + +/** + * camel_stream_filter_add: + * @filter: Initialised CamelStreamFilter. + * @mf: Filter to perform processing on stream. + * + * Add a new CamelMimeFilter to execute during the processing of this + * stream. Each filter added is processed after the previous one. + * + * Note that a filter should only be added to a single stream + * at a time, otherwise unpredictable results may occur. + * + * Return value: A filter id for this CamelStreamFilter. + **/ +int +camel_stream_filter_add(CamelStreamFilter *filter, CamelMimeFilter *mf) +{ + struct _CamelStreamFilterPrivate *p = _PRIVATE(filter); + struct _filter *fn, *f; + + fn = g_malloc(sizeof(*fn)); + fn->id = p->filterid++; + fn->filter = mf; + gtk_object_ref((GtkObject *)mf); + + /* sure, we could use a GList, but we wouldn't save much */ + f = (struct _filter *)&p->filters; + while (f->next) + f = f->next; + f->next = fn; + fn->next = NULL; + return fn->id; +} + +/** + * camel_stream_filter_remove: + * @filter: Initialised CamelStreamFilter. + * @id: Filter id, as returned from camel_stream_filter_add(). + * + * Remove a processing filter from the stream, by id. + **/ +void +camel_stream_filter_remove(CamelStreamFilter *filter, int id) +{ + struct _CamelStreamFilterPrivate *p = _PRIVATE(filter); + struct _filter *fn, *f; + + f = (struct _filter *)&p->filters; + while (f && f->next) { + fn = f->next; + if (fn->id == id) { + f->next = fn->next; + gtk_object_unref((GtkObject *)fn->filter); + g_free(fn); + } + f = f->next; + } +} + +static gint do_read (CamelStream *stream, gchar *buffer, gint n) +{ + CamelStreamFilter *filter = (CamelStreamFilter *)stream; + struct _CamelStreamFilterPrivate *p = _PRIVATE(filter); + int size; + struct _filter *f; + + if (p->filteredlen<=0) { + int presize = READ_SIZE; + + size = camel_stream_read(filter->source, p->buffer, READ_SIZE); + if (size<=0) { + /* this is somewhat untested */ + if (camel_stream_eos(filter->source)) { + f = p->filters; + p->filtered = p->buffer; + p->filteredlen = 0; + while (f) { + camel_mime_filter_complete(f->filter, p->filtered, p->filteredlen, presize, &p->filtered, &p->filteredlen, &presize); + f = f->next; + } + size = p->filteredlen; + } + if (size<=0) + return size; + } else { + f = p->filters; + p->filtered = p->buffer; + p->filteredlen = size; + while (f) { + camel_mime_filter_filter(f->filter, p->filtered, p->filteredlen, presize, &p->filtered, &p->filteredlen, &presize); + f = f->next; + } + } + } + + size = MIN(n, p->filteredlen); + memcpy(buffer, p->filtered, size); + p->filteredlen -= size; + p->filtered += size; + + return size; +} + +static gint do_write (CamelStream *stream, const gchar *buffer, gint n) +{ + /* what semantics *should* this have?? */ + g_warning("Writing to a non-writable stream"); + return -1; +} + +static void do_flush (CamelStream *stream) +{ + /* NO OP */ +} + +static gboolean do_available (CamelStream *stream) +{ + CamelStreamFilter *filter = (CamelStreamFilter *)stream; + struct _CamelStreamFilterPrivate *p = _PRIVATE(filter); + + if (p->filteredlen >0) + return TRUE; + + return camel_stream_available(filter->source); +} + +static gboolean do_eos (CamelStream *stream) +{ + CamelStreamFilter *filter = (CamelStreamFilter *)stream; + struct _CamelStreamFilterPrivate *p = _PRIVATE(filter); + + if (p->filteredlen >0) + return FALSE; + + return camel_stream_eos(filter->source); +} + +static void do_close (CamelStream *stream) +{ + CamelStreamFilter *filter = (CamelStreamFilter *)stream; + struct _CamelStreamFilterPrivate *p = _PRIVATE(filter); + + p->filteredlen = 0; + camel_stream_close(filter->source); +} + +static void do_reset (CamelStream *stream) +{ + CamelStreamFilter *filter = (CamelStreamFilter *)stream; + struct _CamelStreamFilterPrivate *p = _PRIVATE(filter); + struct _filter *f; + + p->filteredlen = 0; + camel_stream_reset(filter->source); + + /* and reset filters */ + f = p->filters; + while (f) { + camel_mime_filter_reset(f->filter); + f = f->next; + } +} + diff --git a/camel/camel-stream-filter.h b/camel/camel-stream-filter.h new file mode 100644 index 0000000000..b63dae275a --- /dev/null +++ b/camel/camel-stream-filter.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2000 Helix Code Inc. + * + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _CAMEL_STREAM_FILTER_H +#define _CAMEL_STREAM_FILTER_H + +#include <gtk/gtk.h> + +#include <camel/camel-stream.h> +#include <camel/camel-mime-filter.h> + +#define CAMEL_STREAM_FILTER(obj) GTK_CHECK_CAST (obj, camel_stream_filter_get_type (), CamelStreamFilter) +#define CAMEL_STREAM_FILTER_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, camel_stream_filter_get_type (), CamelStreamFilterClass) +#define IS_CAMEL_STREAM_FILTER(obj) GTK_CHECK_TYPE (obj, camel_stream_filter_get_type ()) + +typedef struct _CamelStreamFilter CamelStreamFilter; +typedef struct _CamelStreamFilterClass CamelStreamFilterClass; + +struct _CamelStreamFilter { + CamelStream parent; + + CamelStream *source; + + struct _CamelStreamFilterPrivate *priv; +}; + +struct _CamelStreamFilterClass { + CamelStreamClass parent_class; +}; + +guint camel_stream_filter_get_type (void); + +CamelStreamFilter *camel_stream_filter_new_with_stream (CamelStream *stream); + +int camel_stream_filter_add (CamelStreamFilter *filter, CamelMimeFilter *); +void camel_stream_filter_remove (CamelStreamFilter *filter, int id); + +#endif /* ! _CAMEL_STREAM_FILTER_H */ diff --git a/camel/camel-stream.c b/camel/camel-stream.c index 4e7a6c640c..aaa782620e 100644 --- a/camel/camel-stream.c +++ b/camel/camel-stream.c @@ -242,3 +242,45 @@ camel_stream_write_strings (CamelStream *stream, ... ) } va_end (args); } + +/** + * camel_stream_write_to_stream: + * @stream: Source CamelStream. + * @output_stream: Destination CamelStream. + * + * Write all of a stream (until eos) into another stream, in a blocking + * fashion. + * + * FIXME: This really needs to return an error code. + **/ +void +camel_stream_write_to_stream (CamelStream *stream, + CamelStream *output_stream) +{ + gchar tmp_buf[4096]; + gint nb_read; + gint nb_written; + + /* + * default implementation that uses the input + * stream and stream it in a blocking way + * to an output stream. + */ + g_assert (output_stream); + g_assert (stream); + + while (!camel_stream_eos (CAMEL_STREAM (stream))) { + nb_read = camel_stream_read (CAMEL_STREAM (stream), tmp_buf, 4096); + if (nb_read>0) { + nb_written = 0; + + while (nb_written < nb_read) { + int len = camel_stream_write (output_stream, tmp_buf + nb_written, nb_read - nb_written); + /* FIXME: what about length 0? */ + if (len<0) + return; + nb_written += len; + } + } + } +} diff --git a/camel/camel-stream.h b/camel/camel-stream.h index e7375f41aa..bd1123c1c8 100644 --- a/camel/camel-stream.h +++ b/camel/camel-stream.h @@ -94,6 +94,9 @@ void camel_stream_reset (CamelStream *stream); #define camel_stream_write_string(stream, string) camel_stream_write ((stream), (string), strlen (string)) void camel_stream_write_strings (CamelStream *stream, ... ); +/* write a whole stream to another stream, until eof */ +/* FIXME: this should definetly have an error return code */ +void camel_stream_write_to_stream (CamelStream *stream, CamelStream *output_stream); #ifdef __cplusplus } diff --git a/camel/gmime-base64.c b/camel/gmime-base64.c deleted file mode 100644 index 3611cc5247..0000000000 --- a/camel/gmime-base64.c +++ /dev/null @@ -1,106 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Base64 handlers - * - * Author: - * Miguel de Icaza (miguel@kernel.org) - */ -#include <config.h> -#include "gmime-base64.h" - -#define BSIZE 512 - -/* - * 64-based alphabet used by the the Base64 enconding - */ -static char *base64_alphabet = -"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -/** - * gmime_encode_base64_to_stream: - * @input: The data source to be encoded in base64 format - * @output: Where to put the encoded information in. - * - * This routine encodes the information pulled from @input using - * base64 encoding and stores it on the @output CamelStream object - */ -void -gmime_encode_base64_to_stream (CamelStream *input, CamelStream *output) -{ - char buffer [BSIZE]; - char obuf [80]; /* Output is limited to 76 characters, rfc2045 */ - int n, i, j, state; - int keep = 0; - - state = 0; - j = 0; - while ((n = camel_stream_read (input, buffer, sizeof (buffer))) > 0){ - for (i = 0; i < n; i++, state++){ - char c = buffer [i]; - - switch (state % 3){ - case 0: - obuf [j++] = base64_alphabet [c >> 2]; - keep = (c & 3) << 4; - break; - case 1: - obuf [j++] = base64_alphabet [keep | (c >> 4)]; - keep = (c & 0xf) << 2; - break; - case 2: - obuf [j++] = base64_alphabet [keep | (c >> 6)]; - obuf [j++] = base64_alphabet [c & 0x3f]; - break; - } - - if (j == 72){ - obuf [j++] = '\r'; - obuf [j++] = '\n'; - camel_stream_write (output, obuf, j); - j = 0; - } - } - } - - switch (state % 3){ - case 0: - /* full output, nothing left to do */ - break; - - case 1: - obuf [j++] = base64_alphabet [keep]; - obuf [j++] = '='; - obuf [j++] = '='; - break; - - case 2: - obuf [j++] = base64_alphabet [keep]; - obuf [j++] = '='; - break; - } - camel_stream_write (output, obuf, j); - camel_stream_flush (output); -} - - - - - - - -/** - * gmime_decode_base64: - * @input: A buffer in base64 format. - * @output: Destination where the decoded information is stored. - * - * This routine decodes the base64 information pulled from @input - * and stores it on the @output CamelStream object. - */ -void -gmime_decode_base64 (CamelStream *input, CamelStream *output) -{ -} - - - - diff --git a/camel/gmime-base64.h b/camel/gmime-base64.h deleted file mode 100644 index 5dca9c0ff9..0000000000 --- a/camel/gmime-base64.h +++ /dev/null @@ -1,16 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Base64 handlers - * - * Author: - * Miguel de Icaza (miguel@kernel.org) - */ - -#ifndef _GMIME_BASE64_H -#define _GMIME_BASE64_H - -#include "camel-stream.h" - -void gmime_encode_base64_to_stream (CamelStream *input, CamelStream *output); - -#endif diff --git a/camel/gmime-content-field.c b/camel/gmime-content-field.c index 02b44ab7be..6d0abce4e8 100644 --- a/camel/gmime-content-field.c +++ b/camel/gmime-content-field.c @@ -28,7 +28,7 @@ #include "gmime-content-field.h" #include "string-utils.h" #include <string.h> -#include "hash-table-utils.h" +#include "camel-mime-utils.h" /** * gmime_content_field_new: Creates a new GMimeContentField object @@ -49,44 +49,13 @@ gmime_content_field_new (const gchar *type, const gchar *subtype) GMimeContentField *ctf; ctf = g_new (GMimeContentField, 1); - ctf->type = g_strdup (type); - ctf->subtype = g_strdup (subtype); - ctf->parameters = g_hash_table_new (g_strcase_hash, g_strcase_equal); + ctf->content_type = header_content_type_new(type, subtype); + ctf->type = ctf->content_type->type; + ctf->subtype = ctf->content_type->subtype; ctf->ref = 1; - return ctf; } - -static void -_free_parameter (gpointer name, gpointer value, gpointer user_data) -{ - g_free (name); - g_free (value); -} - -/** - * gmime_content_field_free: free a GMimeContentField object - * @content_field: GMimeContentField object - * - * This method destroys the object and should be used very carefully. - * Use gmime_content_field_unref instead. - * - **/ -void -gmime_content_field_free (GMimeContentField *content_field) -{ - if (!content_field) return; - - g_assert (content_field->ref <= 0); - - g_hash_table_foreach (content_field->parameters, _free_parameter, NULL); - g_free (content_field->type); - g_free (content_field->subtype); - g_hash_table_destroy (content_field->parameters); - g_free (content_field); -} - /** * gmime_content_field_ref: add a reference to a GMimeContentField object * @content_field: GMimeContentField object @@ -100,6 +69,7 @@ void gmime_content_field_ref (GMimeContentField *content_field) { content_field->ref += 1; + header_content_type_ref (content_field->content_type); } /** @@ -118,8 +88,9 @@ gmime_content_field_unref (GMimeContentField *content_field) if (!content_field) return; content_field->ref -= 1; + header_content_type_unref (content_field->content_type); if (content_field->ref <= 0) - gmime_content_field_free (content_field); + g_free (content_field); } @@ -139,44 +110,7 @@ gmime_content_field_unref (GMimeContentField *content_field) void gmime_content_field_set_parameter (GMimeContentField *content_field, const gchar *attribute, const gchar *value) { - gboolean attribute_exists; - gchar *old_attribute; - gchar *old_value; - attribute_exists = g_hash_table_lookup_extended (content_field->parameters, - attribute, - (gpointer *) &old_attribute, - (gpointer *) &old_value); - /** CHECK THAT : is normal to free pointers before insertion ? **/ - if (attribute_exists) { - g_hash_table_remove(content_field->parameters, attribute); - g_free (old_value); - g_free (old_attribute); - } - - g_hash_table_insert (content_field->parameters, g_strdup (attribute), g_strdup (value)); -} - - -/** - * _print_parameter: print a parameter/value pair to a stream as described in RFC 2045 - * @name: name of the parameter - * @value: value of the parameter - * @user_data: CamelStream object to write the text to. - * - * - **/ -static void -_print_parameter (gpointer name, gpointer value, gpointer user_data) -{ - CamelStream *stream = (CamelStream *)user_data; - - camel_stream_write_strings (stream, - "; \n ", - (gchar *)name, - "=", - (gchar *)value, - NULL); - + header_content_type_set_param(content_field->content_type, attribute, value); } /** @@ -189,16 +123,21 @@ _print_parameter (gpointer name, gpointer value, gpointer user_data) void gmime_content_field_write_to_stream (GMimeContentField *content_field, CamelStream *stream) { + struct _header_param *p; + if (!content_field) return; g_assert (stream); - if (content_field->type) { - camel_stream_write_strings (stream, "Content-Type: ", content_field->type, NULL); - if (content_field->subtype) { - camel_stream_write_strings (stream, "/", content_field->subtype, NULL); - } + if (content_field->content_type) { + camel_stream_write_strings (stream, "Content-Type: ", content_field->content_type->type?content_field->content_type->type:"text", + "/", content_field->content_type->subtype?content_field->content_type->subtype:"plain", NULL); + /* print all parameters */ - g_hash_table_foreach (content_field->parameters, _print_parameter, stream); + p = content_field->content_type->params; + while (p) { + camel_stream_write_strings (stream, ";\n ", p->name, "= \"", p->value, "\"", NULL); + p = p->next; + } camel_stream_write_string (stream, "\n"); } } @@ -219,23 +158,15 @@ gmime_content_field_get_mime_type (GMimeContentField *content_field) { gchar *mime_type; - if (!content_field->type) return NULL; + if (!content_field->content_type->type) return NULL; - if (content_field->subtype) - mime_type = g_strdup_printf ("%s/%s", content_field->type, content_field->subtype); + if (content_field->content_type->subtype) + mime_type = g_strdup_printf ("%s/%s", content_field->content_type->type, content_field->content_type->subtype); else - mime_type = g_strdup (content_field->type); + mime_type = g_strdup (content_field->content_type->type); return mime_type; } -static void -___debug_print_parameter (gpointer name, gpointer value, gpointer user_data) -{ - - printf ("****** parameter \"%s\"=\"%s\"\n", (gchar *)name, (gchar *)value); - -} - /** * gmime_content_field_get_parameter: return the value of a mime type parameter * @content_field: content field object @@ -252,22 +183,10 @@ ___debug_print_parameter (gpointer name, gpointer value, gpointer user_data) const gchar * gmime_content_field_get_parameter (GMimeContentField *content_field, const gchar *name) { - const gchar *parameter; - const gchar *old_name; - gboolean parameter_exists; + g_assert (content_field); - g_assert (content_field->parameters); g_assert (name); - /* parameter = (const gchar *)g_hash_table_lookup (content_field->parameters, name); */ - parameter_exists = g_hash_table_lookup_extended (content_field->parameters, - name, - (gpointer *) &old_name, - (gpointer *) ¶meter); - if (!parameter_exists) { - g_hash_table_foreach (content_field->parameters, ___debug_print_parameter, NULL); - return NULL; - } - return parameter; + return header_content_type_param(content_field->content_type, name); } @@ -287,76 +206,39 @@ gmime_content_field_get_parameter (GMimeContentField *content_field, const gchar void gmime_content_field_construct_from_string (GMimeContentField *content_field, const gchar *string) { - gint first, len; - gint i=0; - gchar *type, *subtype; - gchar *param_name, *param_value; - gboolean param_end; - + struct _header_content_type *new; + g_assert (string); g_assert (content_field); - - g_free (content_field->type); - g_free (content_field->subtype); - - - first = 0; - len = strlen (string); - if (!len) - return; - - /* find the type */ - while ( (i<len) && (!strchr ("/;", string[i])) ) i++; - - if (i == 0) - return; - - type = g_strndup (string, i); - string_trim (type, " \t\"", STRING_TRIM_STRIP_TRAILING | STRING_TRIM_STRIP_LEADING); - content_field->type = type; - if (i >= len-1) { - content_field->subtype = NULL; - return; - } - - first = i+1; - /* find the subtype, if any */ - if (string[i++] == '/') { - while ( (i<len) && (string[i] != ';') ) i++; - if (i != first) { - subtype = g_strndup (string+first, i-first); - string_trim (subtype, " \t\"", STRING_TRIM_STRIP_TRAILING | STRING_TRIM_STRIP_LEADING); - content_field->subtype = subtype; - if (i >= len-1) - return; - } - } - first = i+1; - - /* parse parameters list */ - param_end = FALSE; - do { - while ( (i<len) && (string[i] != '=') ) i++; - if ((i == len) || (i==first)) param_end = TRUE; - else { - /* we have found parameter name */ - param_name = g_strndup (string+first, i-first); - string_trim (param_name, " \"", STRING_TRIM_STRIP_TRAILING | STRING_TRIM_STRIP_LEADING); - i++; - first = i; - /* Let's find parameter value */ - while ( (i<len) && (string[i] != ';') ) i++; - if (i != first) param_value = g_strndup (string+first, i-first); - else param_value = g_strdup (""); - string_trim (param_value, " \t\"", STRING_TRIM_STRIP_TRAILING | STRING_TRIM_STRIP_LEADING); - gmime_content_field_set_parameter (content_field, param_name, param_value); - g_free (param_name); - g_free (param_value); - i++; - first = i; - } - } while ((!param_end) && (first < len)); + new = header_content_type_decode(string); + if (content_field->content_type) + header_content_type_unref(content_field->content_type); + if (new == NULL) { + new = header_content_type_new(NULL, NULL); + g_warning("Cannot parse content-type string: %s", string); + } + content_field->content_type = new; + content_field->type = new->type; + content_field->subtype = new->subtype; } +/** + * gmime_content_field_is_type: + * @content_field: An initialised GMimeContentField. + * @type: MIME Major type name. + * @subtype: MIME subtype. + * + * Returns true if the content_field is of the type @type and subtype @subtype. + * If @subtype is the special wildcard "*", then it will match any type. + * + * If the @content_field is empty, then it will match "text/plain", or "text/ *". + * + * Return value: + **/ +int +gmime_content_field_is_type (GMimeContentField *content_field, const char *type, const char *subtype) +{ + return header_content_type_is(content_field->content_type, type, subtype); +} diff --git a/camel/gmime-content-field.h b/camel/gmime-content-field.h index 44d0674bf5..5a28d0fedb 100644 --- a/camel/gmime-content-field.h +++ b/camel/gmime-content-field.h @@ -36,13 +36,15 @@ extern "C" { #include <glib.h> #include <stdio.h> -#include "camel-stream.h" +#include <camel/camel-stream.h> +#include <camel/camel-mime-utils.h> typedef struct { + struct _header_content_type *content_type; - gchar *type; - gchar *subtype; - GHashTable *parameters; + /* these should be deprecated (use the accessors) */ + char *type; /* these are only copies of the ones in content_type */ + char *subtype; gint ref; @@ -59,6 +61,8 @@ void gmime_content_field_free (GMimeContentField *content_field); gchar * gmime_content_field_get_mime_type (GMimeContentField *content_field); const gchar *gmime_content_field_get_parameter (GMimeContentField *content_field, const gchar *name); +int gmime_content_field_is_type (GMimeContentField *content_field, const char *type, const char *subtype); + #ifdef __cplusplus } diff --git a/camel/gmime-rfc2047.c b/camel/gmime-rfc2047.c deleted file mode 100644 index 339a54d521..0000000000 --- a/camel/gmime-rfc2047.c +++ /dev/null @@ -1,491 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* gmime-rfc2047.c: implemention of RFC2047 */ - -/* - * Author : - * Bertrand Guiheneuf <bertrand@helixcode.com> - * - * Copyright 1999, 2000 Helix Code, Inc. (http://www.helixcode.com) - * - * 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 - * - */ - -/* - * Authors: Robert Brady <rwb197@ecs.soton.ac.uk> - */ - -#include <stdio.h> -#include <ctype.h> -#include <unicode.h> -#include <string.h> - -#include "gmime-rfc2047.h" - -#define NOT_RANKED -1 - -/* This should be changed ASAP to use the base64 code Miguel comitted */ - -const char *base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -static unsigned char base64_rank[256]; -static int base64_rank_table_built; -static void build_base64_rank_table (void); - -static int -hexval (gchar c) { - if (isdigit (c)) return c-'0'; - c = tolower (c); - return c - 'a' + 10; -} - -static gchar * -decode_quoted (const gchar *text, const gchar *end) -{ - gchar *to = malloc(end - text + 1), *to_2 = to; - - if (!to) return NULL; - while (*text && text < end) { - if (*text == '=') { - gchar a = hexval (text[1]); - gchar b = hexval (text[2]); - int c = (a << 4) + b; - *to = c; - to++; - text+=3; - } else if (*text == '_') { - *to = ' '; - to++; - text++; - } else { - *to = *text; - to++; - text++; - } - } - return to_2; -} - -static gchar * -decode_base64 (const gchar *data, const gchar *end) -{ - unsigned short pattern = 0; - int bits = 0; - int delimiter = '='; - gchar x; - gchar *buffer = g_malloc((end - data) * 3); - gchar *t = buffer; - int Q = 0; - - if (!buffer) return NULL; - - while (*data != delimiter) { - x = base64_rank[(unsigned char)(*data++)]; - if (x == NOT_RANKED) - continue; - pattern <<= 6; - pattern |= x; - bits += 6; - if (bits >= 8) { - x = (pattern >> (bits - 8)) & 0xff; - *t++ = x; - Q++; - bits -= 8; - } - } - *t = 0; - return buffer; -} - -static void -build_base64_rank_table (void) -{ - int i; - - if (!base64_rank_table_built) { - for (i = 0; i < 256; i++) - base64_rank[i] = NOT_RANKED; - for (i = 0; i < 64; i++) - base64_rank[(int) base64_alphabet[i]] = i; - base64_rank_table_built = 1; - } -} - - -static gchar * -rfc2047_decode_word (const gchar *data, const gchar *into_what) -{ - const char *charset = strstr (data, "=?"), *encoding, *text, *end; - - char *buffer, *b, *cooked_data; - - buffer = g_malloc (strlen(data) * 2); - b = buffer; - - if (!charset) return strdup (data); - charset+=2; - - encoding = strchr (charset, '?'); - if (!encoding) return strdup (data); - encoding++; - - text = strchr(encoding, '?'); - if (!text) return strdup (data); - text++; - - end = strstr(text, "?="); - if (!end) return strdup (data); - - b[0] = 0; - - if (toupper(*encoding)=='Q') - cooked_data = decode_quoted (text, end); - else if (toupper (*encoding)=='B') - cooked_data = decode_base64 (text, end); - else - return g_strdup(data); - - { - char *c = strchr (charset, '?'); - char *q = g_malloc (c - charset + 1); - char *cook_2 = cooked_data; - int cook_len = strlen (cook_2); - int b_len = 4096; - unicode_iconv_t i; - strncpy (q, charset, c - charset); - q[c - charset] = 0; - i = unicode_iconv_open (into_what, q); - if (!i) { - g_free (q); - return g_strdup (buffer); - } - if (unicode_iconv (i, &cook_2, &cook_len, &b, &b_len)==-1) - /* FIXME : use approximation code if we can't convert it properly. */ - ; - unicode_iconv_close (i); - *b = 0; - } - - return g_strdup (buffer); -} - -static const gchar * -find_end_of_encoded_word (const gchar *data) -{ - /* We can't just search for ?=, - because of the case : - "=?charset?q?=ff?=" :( */ - if (!data) return NULL; - data = strstr (data, "=?"); - if (!data) return NULL; - data = strchr(data+2, '?'); - if (!data) return NULL; - data = strchr (data+1, '?'); - if (!data) return NULL; - data = strstr (data+1, "?="); - if (!data) return NULL; - return data + 2; -} - -gchar * -gmime_rfc2047_decode (const gchar *data, const gchar *into_what) -{ - char *buffer = malloc (strlen(data) * 4), *b = buffer; - - int was_encoded_word = 0; - - build_base64_rank_table (); - - while (data && *data) { - char *word_start = strstr (data, "=?"), *decoded; - if (!word_start) { - strcpy (b, data); - b[strlen (data)] = 0; - return buffer; - } - if (word_start != data) { - - if (strspn (data, " \t\n\r") != (word_start - data)) { - strncpy (b, data, word_start - data); - b += word_start - data; - *b = 0; - } - } - decoded = rfc2047_decode_word (word_start, into_what); - strcpy (b, decoded); - b += strlen (decoded); - *b = 0; - g_free (decoded); - - data = find_end_of_encoded_word (data); - } - - *b = 0; - return buffer; -} - -#define isnt_ascii(a) ((a) <= 0x1f || (a) >= 0x7f) - -static int -rfc2047_clean (const gchar *string, const gchar *max) -{ - /* if (strstr (string, "?=")) return 1; */ - while (string < max) { - if (isnt_ascii ((unsigned char)*string)) - return 0; - string++; - } - return 1; -} - -static gchar * -encode_word (const gchar *string, int length, const gchar *said_charset) -{ - const gchar *max = string + length; - if (rfc2047_clean(string, max)) { - /* don't bother encoding it if it has no odd characters in it */ - return g_strndup (string, length); - } - { - char *temp = malloc (length * 4 + 1), *t = temp; - t += sprintf (t, "=?%s?q?", said_charset); - while (string < max) { - if (*string == ' ') - *(t++) = '_'; - else if ((*string <= 0x1f) || (*string >= 0x7f) || (*string == '=') || (*string == '?')) - t += sprintf (t, "=%2x", (unsigned char)*string); - else - *(t++) = *string; - - string++; - } - t += sprintf (t, "?="); - *t = 0; - return temp; - } -} - -static int -words_in(char *a) -{ - int words = 1; - while (*a) { - if (*(a++)==' ') - words++; - } - return words; -} - -struct word_data { - const char *word; - int word_length; - const char *to_encode_in; - char *encoded; - enum { - wt_None, - wt_Address, - } type; -}; - -static int string_can_fit_in(const char *a, int length, const char *charset) -{ - while (length--) { - if (*a < 0x1f || *a >= 0x7f) return 0; - a++; - } - return 1; -} - -static void -show_entry(struct word_data *a) -{ - a->type = wt_None; - - if (string_can_fit_in(a->word, a->word_length, "US-ASCII")) - a->to_encode_in = "US-ASCII"; - - if (a->word[0]=='<' && a->word[a->word_length-1]=='>') { - a->type = wt_Address; - } -} - -static void -break_into_words(const char *string, struct word_data *a, int words) -{ - int i; - for (i=0;i<words;i++) { - - char *next_space = strchr(string, ' '); - - if (!next_space) { - a[i].word = string; - a[i].word_length = strlen(string); - a[i].to_encode_in = NULL; /* i.e. the default */ - - show_entry(a+i); - - return; - } - - a[i].word = string; - a[i].word_length = next_space - string; - a[i].to_encode_in = NULL; - - show_entry(a+i); - - string = next_space + 1; - - } -} - -static void -join_words(struct word_data *a, int words) -{ - int i; - for (i=(words-1);i>0;i--) { - if (a[i].to_encode_in == a[i-1].to_encode_in) { - a[i-1].word_length += 1 + a[i].word_length; - a[i].word = 0; - a[i].word_length = 0; - } - - } -} - -static void show_words(struct word_data *words, int count) -{ - int i; - for (i=0;i<count;i++) - if (words[i].word) - show_entry(words+i); -} - -gchar * -gmime_rfc2047_encode (const gchar *string, const gchar *charset) -{ - int temp_len = strlen (string)*4 + 1, word_count; - char *temp = g_malloc (temp_len), *temp_2 = temp; - int string_length = strlen (string); - char *encoded = NULL, *p; - struct word_data *words; - - /* first, let us convert to UTF-8 */ - unicode_iconv_t i = unicode_iconv_open ("UTF-8", charset); - unicode_iconv (i, &string, &string_length, &temp_2, &temp_len); - unicode_iconv_close (i); - - /* null terminate it */ - *temp_2 = 0; - - /* now encode it as if it were a single word */ - - word_count = words_in ( temp ); - - words = g_malloc(sizeof (struct word_data) * word_count); - break_into_words(temp, words, word_count); - - join_words(words, word_count); - - show_words(words, word_count); - - { - size_t len = 0; - int c = 0; - for (c = 0;c<word_count;c++) { - if (words[c].word) - { - words[c].encoded = encode_word(words[c].word, words[c].word_length, - words[c].to_encode_in ? words[c].to_encode_in : - "UTF-8"); - len += strlen(words[c].encoded) + 1; - } - } - - { - encoded = g_malloc(len+1); - p = encoded; - for (c = 0; c < word_count;c++) if (words[c].word) { - strcpy(p, words[c].encoded); - p += strlen(p); - strcpy(p, " "); - p++; - } - *p = 0; - } - } - - - /* - - real algorithm : - - we need to - - split it into words - - identify portions that have NOT to be encoded (i.e. <> and the comment starter/ender ) - - identify the best character set for each word - - merge words which share a character set, allow jumping and merging with words which - would be ok to encode in non-US-ASCII. - - if we have to use 2 character sets, try and collapse them into one. - - (e.g. if one word contains letters in latin-1, and another letters in latin-2, use - latin-2 for the first word as well if possible). - - finally : - - if utf-8 will still be used, use it for everything. - - and then, at last, generate the encoded text, using base64/quoted-printable for - each word depending upon which is more efficient. - - TODO : - create a priority list of encodings - - i.e. - - US-ASCII, ISO-8859-1, ISO-8859-2, ISO-8859-3, KOI8, - - Should survey for most popular charsets : - what do people usually use for the following scripts? - - * Chinese/Japanese/Korean - * Greek - * Cyrillic - - (any other scripts commonly used in mail/news?) - - This algorithm is probably far from optimal, but should be - reasonably efficient for simple cases. (and almost free if - the text is just in US-ASCII : like 99% of the text that will - pass through it) - - - - current status : - - Algorithm now partially implemented. - - */ - - g_free(words); - g_free(temp); - - return encoded; -} diff --git a/camel/gmime-rfc2047.h b/camel/gmime-rfc2047.h deleted file mode 100644 index a4f1bc02a2..0000000000 --- a/camel/gmime-rfc2047.h +++ /dev/null @@ -1,52 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* gmime-rfc2047.c: implemention of RFC2047 */ - -/* - * Author : - * Bertrand Guiheneuf <bertrand@helixcode.com> - * - * Copyright 1999, 2000 Helix Code, Inc. (http://www.helixcode.com) - * - * 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 - * - */ - -/* - * Authors: Robert Brady <rwb197@ecs.soton.ac.uk> - */ - -#ifndef GMIME_RFC2047_H -#define GMIME_RFC2047_H 1 -#include <glib.h> - -gchar *gmime_rfc2047_decode (const gchar *text, const gchar* charset); -gchar *gmime_rfc2047_encode (const gchar *text, const gchar* charset); - -/* - * pass text and charset, (e.g. "UTF-8", or "ISO-8859-1"), and - * it will encode or decode text according to RFC2047 - * - * You will need to link with libunicode for these. - * - * TODO : Make it so that if charset==NULL, the charset specified (either - * implicitly or explicity) in the locale is used. - * - * TODO : Run torture tests and fix the buffer overruns in these functions. - * - * The caller will need to free the memory for the string. - */ - -#endif /* GMIME_RFC2047_H */ diff --git a/camel/providers/Makefile.am b/camel/providers/Makefile.am index 888a5d7bb3..7c520a8b8b 100644 --- a/camel/providers/Makefile.am +++ b/camel/providers/Makefile.am @@ -1,6 +1,6 @@ ## Process this file with automake to produce Makefile.in -SUBDIRS = mbox pop3 sendmail smtp nntp +SUBDIRS = mbox pop3 sendmail smtp # these ones are disabled for the moment. # MH maildir imap smtp diff --git a/camel/providers/mbox/Makefile.am b/camel/providers/mbox/Makefile.am index e3234ce228..4dbe3ed93d 100644 --- a/camel/providers/mbox/Makefile.am +++ b/camel/providers/mbox/Makefile.am @@ -24,22 +24,22 @@ libcamelmbox_la_SOURCES = \ camel-mbox-parser.c \ camel-mbox-provider.c \ camel-mbox-store.c \ - camel-mbox-summary.c \ camel-mbox-search.c \ + camel-mbox-summary.c \ camel-mbox-utils.c libcamelmboxinclude_HEADERS = \ camel-mbox-folder.h \ camel-mbox-parser.h \ camel-mbox-store.h \ - camel-mbox-summary.h \ camel-mbox-search.h \ + camel-mbox-summary.h \ camel-mbox-utils.h - libcamelmbox_la_LDFLAGS = -version-info 0:0:0 -rpath $(libdir) libcamelmbox_la_LIBADD = $(top_builddir)/e-util/libeutil.la $(top_builddir)/libibex/libibex.la $(UNICODE_LIBS) #libcamelmbox_la_LIBADD = $(top_builddir)/libibex/libibex.la $(UNICODE_LIBS) EXTRA_DIST = + diff --git a/camel/providers/mbox/camel-mbox-folder.c b/camel/providers/mbox/camel-mbox-folder.c index 9cd055a658..2fa3ddc9da 100644 --- a/camel/providers/mbox/camel-mbox-folder.c +++ b/camel/providers/mbox/camel-mbox-folder.c @@ -2,9 +2,10 @@ /* camel-mbox-folder.c : Abstract class for an email folder */ /* - * Author : Bertrand Guiheneuf <bertrand@helixcode.com> + * Authors: Bertrand Guiheneuf <bertrand@helixcode.com> + * Michael Zucchi <notzed@helixcode.com> * - * Copyright (C) 1999 Helix Code . + * Copyright (C) 1999, 2000 Helix Code Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -48,6 +49,8 @@ #include "camel-exception.h" +#define d(x) + static CamelFolderClass *parent_class=NULL; /* Returns the class for a CamelMboxFolder */ @@ -59,8 +62,6 @@ static CamelFolderClass *parent_class=NULL; static void _init (CamelFolder *folder, CamelStore *parent_store, CamelFolder *parent_folder, const gchar *name, gchar separator, CamelException *ex); -static void _set_name(CamelFolder *folder, const gchar *name, CamelException *ex); - static void _open (CamelFolder *folder, CamelFolderOpenMode mode, CamelException *ex); static void _close (CamelFolder *folder, gboolean expunge, CamelException *ex); @@ -80,6 +81,8 @@ static void _copy_message_to (CamelFolder *folder, CamelMimeMessage *message, Ca static const gchar *_get_message_uid (CamelFolder *folder, CamelMimeMessage *message, CamelException *ex); #endif +GPtrArray *summary_get_message_info (CamelFolder *folder, int first, int count); + static void _finalize (GtkObject *object); static void @@ -94,7 +97,6 @@ camel_mbox_folder_class_init (CamelMboxFolderClass *camel_mbox_folder_class) /* virtual method overload */ camel_folder_class->init = _init; - camel_folder_class->set_name = _set_name; camel_folder_class->open = _open; camel_folder_class->close = _close; camel_folder_class->exists = _exists; @@ -117,12 +119,12 @@ camel_mbox_folder_class_init (CamelMboxFolderClass *camel_mbox_folder_class) camel_folder_class->search_complete = camel_mbox_folder_search_complete; camel_folder_class->search_cancel = camel_mbox_folder_search_cancel; + camel_folder_class->get_message_info = summary_get_message_info; + gtk_object_class->finalize = _finalize; } - - static void _finalize (GtkObject *object) { @@ -134,10 +136,6 @@ _finalize (GtkObject *object) GTK_OBJECT_CLASS (parent_class)->finalize (object); } - - - - GtkType camel_mbox_folder_get_type (void) { @@ -162,154 +160,73 @@ camel_mbox_folder_get_type (void) return camel_mbox_folder_type; } - - - - - static void _init (CamelFolder *folder, CamelStore *parent_store, CamelFolder *parent_folder, const gchar *name, gchar separator, CamelException *ex) { + CamelMboxFolder *mbox_folder = (CamelMboxFolder *)folder; + const gchar *root_dir_path; + /* call parent method */ parent_class->init (folder, parent_store, parent_folder, name, separator, ex); - if (camel_exception_get_id (ex)) return; + if (camel_exception_get_id (ex)) + return; /* we assume that the parent init method checks for the existance of @folder */ - folder->can_hold_messages = TRUE; folder->can_hold_folders = TRUE; folder->has_summary_capability = TRUE; folder->has_uid_capability = TRUE; folder->has_search_capability = TRUE; - folder->summary = NULL; -} - + mbox_folder->summary = NULL; -/* internal method used to : - - test for the existence of a summary file - - test the sync between the summary and the mbox file - - load the summary or create it if necessary -*/ -static void -_check_get_or_maybe_generate_summary_file (CamelMboxFolder *mbox_folder, - CamelException *ex) -{ - CamelFolder *folder = CAMEL_FOLDER (mbox_folder); - CamelMboxSummary *summ; - GArray *message_info_array; - gint mbox_file_fd; - guint32 next_uid; - guint32 file_size; - struct stat st; - - folder->summary = NULL; - - /* Test for the existence and up-to-dateness of the summary file. */ - if (access (mbox_folder->summary_file_path, F_OK) == 0) { - summ = camel_mbox_summary_load (mbox_folder->summary_file_path, - ex); - if (summ) { - if (stat (mbox_folder->folder_file_path, &st) == 0 && - summ->mbox_file_size == st.st_size && - summ->mbox_modtime == st.st_mtime) - folder->summary = CAMEL_FOLDER_SUMMARY (summ); - else - gtk_object_destroy (GTK_OBJECT (summ)); - } else { - /* Bad summary file */ - if (camel_exception_get_id (ex) != - CAMEL_EXCEPTION_FOLDER_SUMMARY_INVALID) - return; - camel_exception_clear (ex); - } - } - - /* In the case where the summary does not exist (or was the - * wrong version), or is not in sync with the mbox file, - * regenerate it. - */ - if (folder->summary == NULL) { - /* Parse the mbox folder and get some information - * about the messages. - */ - mbox_file_fd = open (mbox_folder->folder_file_path, O_RDONLY); - if (mbox_file_fd != -1) { - message_info_array = - camel_mbox_parse_file (mbox_file_fd, "From ", - 0, &file_size, - &next_uid, TRUE, - NULL, 0, ex); - close (mbox_file_fd); - if (camel_exception_get_id (ex)) - return; - - next_uid = camel_mbox_write_xev (mbox_folder, - mbox_folder->folder_file_path, - message_info_array, - &file_size, - next_uid, ex); - if (camel_exception_get_id (ex)) { - /* ** FIXME : free the preparsed information */ - return; - } + /* now set the name info */ + g_free (mbox_folder->folder_file_path); + g_free (mbox_folder->folder_dir_path); + g_free (mbox_folder->index_file_path); - summ = CAMEL_MBOX_SUMMARY (gtk_object_new (camel_mbox_summary_get_type (), NULL)); - summ->message_info = parsed_information_to_mbox_summary (message_info_array); - summ->nb_message = summ->message_info->len; - summ->next_uid = next_uid; - summ->mbox_file_size = file_size; - /* **FIXME : Free the parsed information structure */ - } else { - summ = CAMEL_MBOX_SUMMARY (gtk_object_new (camel_mbox_summary_get_type (), NULL)); - summ->message_info = g_array_new (FALSE, FALSE, sizeof (CamelMboxSummaryInformation)); - summ->nb_message = 0; - summ->next_uid = 0; - summ->mbox_file_size = 0; - } + root_dir_path = camel_mbox_store_get_toplevel_dir (CAMEL_MBOX_STORE(folder->parent_store)); - folder->summary = CAMEL_FOLDER_SUMMARY (summ); - } + mbox_folder->folder_file_path = g_strdup_printf ("%s/%s", root_dir_path, folder->full_name); + mbox_folder->summary_file_path = g_strdup_printf ("%s/%s-ev-summary", root_dir_path, folder->full_name); + mbox_folder->folder_dir_path = g_strdup_printf ("%s/%s.sdb", root_dir_path, folder->full_name); + mbox_folder->index_file_path = g_strdup_printf ("%s/%s.ibex", root_dir_path, folder->full_name); } - - static void _open (CamelFolder *folder, CamelFolderOpenMode mode, CamelException *ex) { CamelMboxFolder *mbox_folder = CAMEL_MBOX_FOLDER (folder); + /* call parent class */ + parent_class->open (folder, mode, ex); + if (camel_exception_get_id(ex)) + return; + mbox_folder->index = ibex_open(mbox_folder->index_file_path, O_CREAT|O_RDWR, 0600); if (mbox_folder->index == NULL) { g_warning("Could not open/create index file: %s: indexing will not function", strerror(errno)); } - /* call parent class */ - parent_class->open (folder, mode, ex); - if (camel_exception_get_id(ex)) + mbox_folder->summary = camel_mbox_summary_new(mbox_folder->summary_file_path, mbox_folder->folder_file_path, mbox_folder->index); + if (mbox_folder->summary == NULL) { + camel_exception_set (ex, + CAMEL_EXCEPTION_FOLDER_INVALID, /* FIXME: right error code */ + "Could not create summary"); return; - -#if 0 - /* get (or create) uid list */ - if (!(mbox_load_uid_list (mbox_folder) > 0)) - mbox_generate_uid_list (mbox_folder); -#endif - - _check_get_or_maybe_generate_summary_file (mbox_folder, ex); + } + camel_mbox_summary_load(mbox_folder->summary); } - static void _close (CamelFolder *folder, gboolean expunge, CamelException *ex) { CamelMboxFolder *mbox_folder = CAMEL_MBOX_FOLDER (folder); - CamelMboxSummary *mbox_summary = CAMEL_MBOX_SUMMARY (folder->summary); - struct stat st; /* call parent implementation */ parent_class->close (folder, expunge, ex); @@ -317,47 +234,15 @@ _close (CamelFolder *folder, gboolean expunge, CamelException *ex) /* save index */ if (mbox_folder->index) { ibex_close(mbox_folder->index); + mbox_folder->index = NULL; } - - /* Update the summary and save it to disk */ - if (stat (mbox_folder->folder_file_path, &st) == 0) { - mbox_summary->mbox_file_size = st.st_size; - mbox_summary->mbox_modtime = st.st_mtime; - } - camel_mbox_summary_save (mbox_summary, - mbox_folder->summary_file_path, ex); -} - - - - -static void -_set_name (CamelFolder *folder, const gchar *name, CamelException *ex) -{ - CamelMboxFolder *mbox_folder = CAMEL_MBOX_FOLDER (folder); - const gchar *root_dir_path; - - /* call default implementation */ - parent_class->set_name (folder, name, ex); - if (camel_exception_get_id (ex)) return; - - g_free (mbox_folder->folder_file_path); - g_free (mbox_folder->folder_dir_path); - g_free (mbox_folder->index_file_path); - - root_dir_path = camel_mbox_store_get_toplevel_dir (CAMEL_MBOX_STORE(folder->parent_store)); - - mbox_folder->folder_file_path = g_strdup_printf ("%s/%s", root_dir_path, folder->full_name); - mbox_folder->summary_file_path = g_strdup_printf ("%s/%s-ev-summary", root_dir_path, folder->full_name); - mbox_folder->folder_dir_path = g_strdup_printf ("%s/%s.sdb", root_dir_path, folder->full_name); - mbox_folder->index_file_path = g_strdup_printf ("%s/%s.ibex", root_dir_path, folder->full_name); + camel_mbox_summary_save (mbox_folder->summary); + camel_mbox_summary_unref (mbox_folder->summary); + mbox_folder->summary = NULL; } - - - - +/* FIXME: clean up this snot */ static gboolean _exists (CamelFolder *folder, CamelException *ex) { @@ -420,18 +305,11 @@ _exists (CamelFolder *folder, CamelException *ex) return exists; } - - - - - - - +/* FIXME: clean up this snot */ static gboolean _create (CamelFolder *folder, CamelException *ex) { CamelMboxFolder *mbox_folder = CAMEL_MBOX_FOLDER (folder); - CamelMboxSummary *summary; const gchar *folder_file_path, *folder_dir_path; mode_t dir_mode = S_IRWXU; gint mkdir_error; @@ -480,13 +358,6 @@ _create (CamelFolder *folder, CamelException *ex) close (creat_fd); - /* create the summary object */ - summary = CAMEL_MBOX_SUMMARY (gtk_object_new (camel_mbox_summary_get_type (), NULL)); - summary->nb_message = 0; - summary->next_uid = 1; - summary->mbox_file_size = 0; - summary->message_info = g_array_new (FALSE, FALSE, sizeof (CamelMboxSummaryInformation)); - return TRUE; /* exception handling for io errors */ @@ -505,7 +376,7 @@ _create (CamelFolder *folder, CamelException *ex) } - +/* FIXME: cleanup */ static gboolean _delete (CamelFolder *folder, gboolean recurse, CamelException *ex) { @@ -605,9 +476,7 @@ _delete (CamelFolder *folder, gboolean recurse, CamelException *ex) return TRUE; } - - - +/* TODO: remove this */ gboolean _delete_messages (CamelFolder *folder, CamelException *ex) { @@ -616,7 +485,6 @@ _delete_messages (CamelFolder *folder, CamelException *ex) const gchar *folder_file_path; gboolean folder_already_exists; int creat_fd; - g_assert(folder!=NULL); /* in the case where the folder does not exist, @@ -667,7 +535,7 @@ _delete_messages (CamelFolder *folder, CamelException *ex) } - +/* FIXME: cleanup */ static GList * _list_subfolders (CamelFolder *folder, CamelException *ex) { @@ -786,138 +654,133 @@ _list_subfolders (CamelFolder *folder, CamelException *ex) return NULL; } - - - static gint _get_message_count (CamelFolder *folder, CamelException *ex) { - gint message_count; + CamelMboxFolder *mbox_folder = (CamelMboxFolder *)folder; g_assert (folder); - g_assert (folder->summary); + g_assert (mbox_folder->summary); - message_count = CAMEL_MBOX_SUMMARY (folder->summary)->nb_message; - - return message_count; + return camel_mbox_summary_message_count(mbox_folder->summary); } +/* + This is a lazy append. + Basically, messages are appended to the end of the mbox, and probably assigned + a new uid (they wont be if copying from a source folder which doesn't have + a uid - which wont happen with the current summariser). + + Indexing/summarising happens when the mbox is next queried. + + Should this set a flag up for subsequent updating?? +*/ + +/* FIXME: this may need some tweaking for performance? */ static void _append_message (CamelFolder *folder, CamelMimeMessage *message, CamelException *ex) { - CamelMboxFolder *mbox_folder = CAMEL_MBOX_FOLDER (folder); - CamelMboxSummary *summary = CAMEL_MBOX_SUMMARY (folder->summary); + CamelMboxFolder *mbox_folder = CAMEL_MBOX_FOLDER (folder), *source_folder; CamelStream *output_stream; - guint32 tmp_file_size; - guint32 next_uid; - gint tmp_file_fd; - GArray *message_info_array; - GArray *mbox_summary_info; - gchar *tmp_message_filename; - gint fd1, fd2; - int i; - - tmp_message_filename = g_strdup_printf ("%s.tmp", - mbox_folder->folder_file_path); - - /* write the message itself */ - output_stream = camel_stream_fs_new_with_name (tmp_message_filename, - CAMEL_STREAM_FS_WRITE); - if (output_stream != NULL) { - camel_stream_write_string (output_stream, "From - \n"); - camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), output_stream); - } - camel_stream_close (output_stream); - gtk_object_unref (GTK_OBJECT (output_stream)); - - /* at this point we have saved the message to a - temporary file, now, we have to add the x-evolution - field and also update the main summary */ - - /* - First : parse the mbox file, but only from the - position where the message has been added, - wich happens to be the last postion in the - mbox file before we added the message. - This position is still stored in the summary - for the moment - */ - next_uid = summary->next_uid; - tmp_file_fd = open (tmp_message_filename, O_RDONLY); - message_info_array = - camel_mbox_parse_file (tmp_file_fd, "From - ", 0, - &tmp_file_size, &next_uid, TRUE, - NULL, 0, ex); - - close (tmp_file_fd); - - /* get the value of the last available UID - as saved in the summary file, again */ - next_uid = summary->next_uid; - - /* make sure all our of message info's have 0 uid - ignore any - set elsewhere */ - for (i=0;i<message_info_array->len;i++) { - g_array_index(message_info_array, CamelMboxParserMessageInfo, i).uid = 0; - } + struct stat st; + off_t seek; + char *xev; + guint32 uid; - /* - OK, this is not very efficient, we should not use the same - method as for parsing an entire mail file, - but I have no time to write a simpler parser - */ - next_uid = camel_mbox_write_xev (mbox_folder, tmp_message_filename, - message_info_array, &tmp_file_size, next_uid, ex); - - if (camel_exception_get_id (ex)) { - /* ** FIXME : free the preparsed information */ + if (stat(mbox_folder->folder_file_path, &st) != 0) { + camel_exception_setv (ex, + CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, /* FIXME: what code? */ + "Cannot append to mbox file: %s", strerror (errno)); return; } - mbox_summary_info = - parsed_information_to_mbox_summary (message_info_array); + /* are we coming from an mbox folder? then we can optimise somewhat ... */ + if (message->folder && IS_CAMEL_MBOX_FOLDER(message->folder)) { + CamelMboxMessageInfo *info; + int sfd, dfd; + off_t pos; - /* store the number of messages as well as the summary array */ - summary->nb_message += 1; - summary->next_uid = next_uid; + /* FIXME: this is pretty ugly - we lookup the message info in the source folder, copy it, + then go back and paste in its real uid. */ + source_folder = (CamelMboxFolder *)message->folder; + info = camel_mbox_summary_uid(source_folder->summary, message->message_uid); - ((CamelMboxSummaryInformation *)(mbox_summary_info->data))->position += - summary->mbox_file_size; - summary->mbox_file_size += tmp_file_size; + d(printf("Copying message directly from %s to %s\n", source_folder->folder_file_path, mbox_folder->folder_file_path)); + d(printf("start = %d, xev = %d\n", ((CamelMboxMessageContentInfo *)info->info.content)->pos, info->xev_offset)); - camel_mbox_summary_append_entries (summary, mbox_summary_info); - g_array_free (mbox_summary_info, TRUE); - + sfd = open(source_folder->folder_file_path, O_RDONLY); + dfd = open(mbox_folder->folder_file_path, O_RDWR|O_CREAT, 0600); + if (lseek(dfd, st.st_size, SEEK_SET) != st.st_size) { + camel_exception_setv (ex, + CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, /* FIXME: what code? */ + "Cannot append to mbox file: %s", strerror (errno)); + close(sfd); + close(dfd); + return; + } + write(dfd, "From - \n", strlen("From - \n")); + camel_mbox_summary_copy_block + (sfd, dfd, ((CamelMboxMessageContentInfo *)info->info.content)->pos, + ((CamelMboxMessageContentInfo *)info->info.content)->endpos - ((CamelMboxMessageContentInfo *)info->info.content)->pos); + if (info->xev_offset != -1) { + pos = st.st_size + (info->xev_offset - ((CamelMboxMessageContentInfo *)info->info.content)->pos) + strlen("From - \n"); + d(printf("Inserting new uid at %d\n", (int)pos)); + if (pos != lseek(dfd, pos, SEEK_SET)) { + ftruncate(dfd, st.st_size); + camel_exception_setv (ex, + CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, /* FIXME: what code? */ + "Cannot append to mbox file: %s", strerror (errno)); + close(sfd); + close(dfd); + return; + } + uid = camel_mbox_summary_next_uid(mbox_folder->summary); + xev = g_strdup_printf("X-Evolution: %08x-%04x", uid, 0); + write(dfd, xev, strlen(xev)); /* FIXME: check return */ + d(printf("header = %s\n", xev)); + g_free(xev); + } + close(sfd); + close(dfd); + return; + } - /* append the temporary file message to the mbox file */ - fd1 = open (tmp_message_filename, O_RDONLY); - fd2 = open (mbox_folder->folder_file_path, - O_WRONLY | O_CREAT | O_APPEND, - 0600); + /* its not an mbox folder, so lets do it the slow way ... */ + output_stream = camel_stream_fs_new_with_name (mbox_folder->folder_file_path, CAMEL_STREAM_FS_WRITE); + if (output_stream == NULL) { + camel_exception_setv (ex, + CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, /* FIXME: what code? */ + "Cannot append to mbox file: %s", strerror (errno)); + return; + } - if (fd2 == -1) { + seek = camel_seekable_stream_seek((CamelSeekableStream *)output_stream, st.st_size, SEEK_SET); + if (seek != st.st_size) { camel_exception_setv (ex, - CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, - "could not open the mbox folder file for appending the message\n" - "\t%s\n" - "Full error is : %s\n", - mbox_folder->folder_file_path, - strerror (errno)); + CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, /* FIXME: what code? */ + "Cannot seek to position in mbox file: %s", strerror (errno)); + gtk_object_unref ((GtkObject *)output_stream); return; } - camel_mbox_copy_file_chunk (fd1, - fd2, - tmp_file_size, - ex); - close (fd1); - close (fd2); + /* assign a new x-evolution header */ + /* FIXME: save flags? */ + camel_medium_remove_header((CamelMedium *)message, "X-Evolution"); + uid = camel_mbox_summary_next_uid(mbox_folder->summary); + xev = g_strdup_printf("%08x-%04x", uid, 0); + camel_medium_add_header((CamelMedium *)message, "X-Evolution", xev); + g_free(xev); + + camel_stream_write_string (output_stream, "From - \n"); + /* FIXME: does this return an error? IT HAS TO FOR THIS TO BE RELIABLE */ + camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), output_stream); + camel_stream_close (output_stream); - /* remove the temporary file */ - unlink (tmp_message_filename); + /* TODO: update the summary so it knows a new message is there to summarise/index */ + /* This is only a performance improvement, the summary is *only* a cache */ - g_free (tmp_message_filename); + gtk_object_unref (GTK_OBJECT (output_stream)); } @@ -926,109 +789,95 @@ _append_message (CamelFolder *folder, CamelMimeMessage *message, CamelException static GList * _get_uid_list (CamelFolder *folder, CamelException *ex) { - GArray *message_info_array; - CamelMboxSummaryInformation *message_info; GList *uid_list = NULL; - int i; - - message_info_array = - CAMEL_MBOX_SUMMARY (folder->summary)->message_info; - - for (i=0; i<message_info_array->len; i++) { - message_info = (CamelMboxSummaryInformation *)(message_info_array->data) + i; - uid_list = g_list_prepend (uid_list, g_strdup_printf ("%u", message_info->uid)); + CamelMboxFolder *mbox_folder = (CamelMboxFolder *)folder; + int i, count; + + /* FIXME: how are these allocated strings ever free'd? */ + count = camel_mbox_summary_message_count(mbox_folder->summary); + for (i=0;i<count;i++) { + CamelMboxMessageInfo *info = camel_mbox_summary_index(mbox_folder->summary, i); + uid_list = g_list_prepend(uid_list, g_strdup(info->info.uid)); } return uid_list; } - - - static CamelMimeMessage * _get_message_by_number (CamelFolder *folder, gint number, CamelException *ex) { - GArray *message_info_array; - CamelMboxSummaryInformation *message_info; - char uidbuf[20]; + CamelMboxFolder *mbox_folder = (CamelMboxFolder *)folder; + CamelMboxMessageInfo *info; - message_info_array = - CAMEL_MBOX_SUMMARY (folder->summary)->message_info; + g_warning("YOUR CODE SHOULD NOT BE GETTING MESSAGES BY NUMBER, CHANGE IT"); - if (number > message_info_array->len) { + info = camel_mbox_summary_index(mbox_folder->summary, number); + if (info == NULL) { camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID, "No such message %d in folder `%s'.", number, folder->name); return NULL; } - message_info = - (CamelMboxSummaryInformation *)(message_info_array->data) + - (number - 1); - sprintf (uidbuf, "%lu", message_info->uid); - - return _get_message_by_uid (folder, uidbuf, ex); + return _get_message_by_uid (folder, info->info.uid, ex); } - static CamelMimeMessage * _get_message_by_uid (CamelFolder *folder, const gchar *uid, CamelException *ex) { - CamelMboxFolder *mbox_folder = CAMEL_MBOX_FOLDER (folder); - GArray *message_info_array; - CamelMboxSummaryInformation *message_info = NULL; - guint32 searched_uid; - int i; - gboolean uid_found; CamelStream *message_stream; CamelMimeMessage *message = NULL; CamelStore *parent_store; + CamelMboxMessageInfo *info; - searched_uid = strtoul (uid, NULL, 10); - - message_info_array = - CAMEL_MBOX_SUMMARY (folder->summary)->message_info; - i=0; - uid_found = FALSE; - - /* first, look for the message that has the searched uid */ - while ((i<message_info_array->len) && (!uid_found)) { - message_info = (CamelMboxSummaryInformation *)(message_info_array->data) + i; - uid_found = (message_info->uid == searched_uid); - i++; + /* get the parent store */ + parent_store = camel_folder_get_parent_store (folder, ex); + if (camel_exception_get_id (ex)) { + return NULL; } - - /* if the uid was not found, raise an exception and return */ - if (!uid_found) { + + /* get the message summary info */ + info = camel_mbox_summary_uid(mbox_folder->summary, uid); + + if (info == NULL) { camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID, "uid %s not found in the folder", uid); return NULL; } - - /* at this point, the message_info structure - contains the informations concerning the - message that was searched for */ - - /* create a stream bound to the message */ + + /* if this has no content, its an error in the library */ + g_assert(info->info.content); + + /* FIXME: more checks below */ + /* create a stream bound to the message position/size */ message_stream = camel_stream_fs_new_with_name_and_bounds (mbox_folder->folder_file_path, CAMEL_STREAM_FS_READ, - message_info->position, - message_info->position + message_info->size); + ((CamelMboxMessageContentInfo *)info->info.content)->pos, + ((CamelMboxMessageContentInfo *)info->info.content)->endpos); + message = camel_mime_message_new(); + camel_data_wrapper_set_input_stream (CAMEL_DATA_WRAPPER (message), message_stream); + /* init other fields? */ + message->folder = folder; + message->message_uid = g_strdup(uid); - /* get the parent store */ - parent_store = camel_folder_get_parent_store (folder, ex); - if (camel_exception_get_id (ex)) { - gtk_object_unref (GTK_OBJECT (message_stream)); - return NULL; - } - - - message = camel_mime_message_new (); - camel_data_wrapper_set_input_stream (CAMEL_DATA_WRAPPER (message), message_stream); - return message; } + +/* get message info for a range of messages */ +GPtrArray *summary_get_message_info (CamelFolder *folder, int first, int count) +{ + GPtrArray *array = g_ptr_array_new(); + int i, maxcount; + CamelMboxFolder *mbox_folder = (CamelMboxFolder *)folder; + + maxcount = camel_mbox_summary_message_count(mbox_folder->summary); + maxcount = MAX(count, maxcount); + for (i=first;i<maxcount;i++) + g_ptr_array_add(array, g_ptr_array_index(mbox_folder->summary->messages, i)); + + return array; +} diff --git a/camel/providers/mbox/camel-mbox-folder.h b/camel/providers/mbox/camel-mbox-folder.h index 52d3fa6e70..f74c51a6c3 100644 --- a/camel/providers/mbox/camel-mbox-folder.h +++ b/camel/providers/mbox/camel-mbox-folder.h @@ -35,8 +35,8 @@ extern "C" { #include <gtk/gtk.h> #include "camel-folder.h" -#include "camel-mbox-summary.h" #include "libibex/ibex.h" +#include "camel-mbox-summary.h" /* #include "camel-store.h" */ @@ -45,7 +45,6 @@ extern "C" { #define CAMEL_MBOX_FOLDER_CLASS(k) (GTK_CHECK_CLASS_CAST ((k), CAMEL_MBOX_FOLDER_TYPE, CamelMboxFolderClass)) #define IS_CAMEL_MBOX_FOLDER(o) (GTK_CHECK_TYPE((o), CAMEL_MBOX_FOLDER_TYPE)) - typedef struct { CamelFolder parent_object; @@ -54,11 +53,11 @@ typedef struct { gchar *folder_dir_path; /* contains the subfolders */ gchar *index_file_path; /* index of body contents */ - GList *uid_array; - ibex *index; /* index for this folder */ int search_id; /* next search id */ GList *searches; /* current searches */ + + CamelMboxSummary *summary; } CamelMboxFolder; diff --git a/camel/providers/mbox/camel-mbox-search.c b/camel/providers/mbox/camel-mbox-search.c index 1a584b8db6..1e134476e5 100644 --- a/camel/providers/mbox/camel-mbox-search.c +++ b/camel/providers/mbox/camel-mbox-search.c @@ -84,10 +84,9 @@ struct _searchcontext { ibex *index; /* index of content for this folder */ #endif - CamelFolderSummary *summary; - const GArray *message_info; + CamelMboxSummary *summary; - CamelMessageInfo *message_current; /* when performing a (match operation */ + CamelMboxMessageInfo *message_current; /* when performing a (match operation */ }; struct _glib_sux_donkeys { @@ -115,7 +114,7 @@ func_body_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, void if (ctx->index) { for (i=0;i<argc && !truth;i++) { if (argv[i]->type == ESEXP_RES_STRING) { - truth = ibex_find_name(ctx->index, ctx->message_current->uid, argv[i]->value.string); + truth = ibex_find_name(ctx->index, ctx->message_current->info.uid, argv[i]->value.string); } else { g_warning("Invalid type passed to body-contains match function"); } @@ -189,19 +188,19 @@ func_match_all(struct _ESExp *f, int argc, struct _ESExpTerm **argv, void *data) r = e_sexp_result_new(ESEXP_RES_ARRAY_PTR); r->value.ptrarray = g_ptr_array_new(); - for (i=0;i<ctx->message_info->len;i++) { + for (i=0;i<ctx->summary->messages->len;i++) { if (argc>0) { - ctx->message_current = &g_array_index(ctx->message_info, CamelMessageInfo, i); + ctx->message_current = g_ptr_array_index(ctx->summary->messages, i); r1 = e_sexp_term_eval(f, argv[0]); if (r1->type == ESEXP_RES_BOOL) { if (r1->value.bool) - g_ptr_array_add(r->value.ptrarray, ctx->message_current->uid); + g_ptr_array_add(r->value.ptrarray, ctx->message_current->info.uid); } else { g_warning("invalid syntax, matches require a single bool result"); } e_sexp_result_free(r1); } else { - g_ptr_array_add(r->value.ptrarray, ctx->message_current->uid); + g_ptr_array_add(r->value.ptrarray, ctx->message_current->info.uid); } } ctx->message_current = NULL; @@ -221,27 +220,28 @@ func_header_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, voi /* are we inside a match-all? */ if (ctx->message_current && argc>1 && argv[0]->type == ESEXP_RES_STRING) { - char *headername, *header; + char *headername, *header = NULL; + char strbuf[32]; int i; /* only a subset of headers are supported .. */ headername = argv[0]->value.string; if (!strcasecmp(headername, "subject")) { - header = ctx->message_current->subject; + header = ctx->message_current->info.subject; } else if (!strcasecmp(headername, "date")) { - header = ctx->message_current->sent_date; + sprintf(strbuf, "%d", (int)ctx->message_current->info.date_sent); + header = strbuf; } else if (!strcasecmp(headername, "from")) { - header = ctx->message_current->sender; + header = ctx->message_current->info.from; } else { g_warning("Performing query on unknown header: %s", headername); - header = NULL; } if (header) { for (i=1;i<argc && !truth;i++) { if (argv[i]->type == ESEXP_RES_STRING && strstr(header, argv[i]->value.string)) { - printf("%s got a match with %s of %s\n", ctx->message_current->uid, header, argv[i]->value.string); + printf("%s got a match with %s of %s\n", ctx->message_current->info.uid, header, argv[i]->value.string); truth = TRUE; break; } @@ -276,6 +276,7 @@ int camel_mbox_folder_search_by_expression(CamelFolder *folder, const char *expr GList *matches = NULL; ESExp *f; ESExpResult *r; + CamelMboxFolder *mbox_folder = (CamelMboxFolder *)folder; /* setup our expression evaluator */ f = e_sexp_new(); @@ -286,7 +287,7 @@ int camel_mbox_folder_search_by_expression(CamelFolder *folder, const char *expr /* setup out context */ ctx->folder = folder; - ctx->summary = camel_folder_get_summary(folder, ex); + ctx->summary = mbox_folder->summary; if (ctx->summary == NULL || camel_exception_get_id (ex)) { printf ("Cannot get summary\n" @@ -296,10 +297,7 @@ int camel_mbox_folder_search_by_expression(CamelFolder *folder, const char *expr return -1; } - gtk_object_ref((GtkObject *)ctx->summary); - /* FIXME: the index should be global to the folder */ - ctx->message_info = CAMEL_MBOX_SUMMARY(ctx->summary)->message_info; ctx->message_current = NULL; ctx->index = CAMEL_MBOX_FOLDER(folder)->index; if (!ctx->index) { @@ -337,7 +335,6 @@ int camel_mbox_folder_search_by_expression(CamelFolder *folder, const char *expr printf("no result!\n"); } - gtk_object_unref((GtkObject *)ctx->summary); gtk_object_unref((GtkObject *)f); i = ctx->id; diff --git a/camel/providers/mbox/camel-mbox-summary.c b/camel/providers/mbox/camel-mbox-summary.c index e15bb13b03..11f1c5779a 100644 --- a/camel/providers/mbox/camel-mbox-summary.c +++ b/camel/providers/mbox/camel-mbox-summary.c @@ -1,420 +1,1258 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ - -/* - * Author : Bertrand Guiheneuf <bertrand@helixcode.com> +/* + * Copyright (C) 2000 Helix Code Inc. * - * Copyright (C) 1999 - 2000 Helix Code . - - * 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. + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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. + * 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 Library 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 + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ - -#include <config.h> - -#include "camel-exception.h" -#include "camel-mbox-folder.h" -#include "camel-mbox-summary.h" -#include "md5-utils.h" - - -#include <sys/stat.h> -#include <unistd.h> #include <sys/types.h> +#include <sys/stat.h> #include <fcntl.h> -#include <dirent.h> +#include <unistd.h> +#include <sys/uio.h> + #include <stdio.h> #include <string.h> + +#include <gtk/gtk.h> + +#include <camel/camel-mime-parser.h> +#include <camel/camel-mime-filter.h> +#include <camel/camel-mime-filter-basic.h> +#include <camel/camel-mime-filter-charset.h> +#include <camel/camel-mime-filter-index.h> + +#include <camel/camel-mime-utils.h> + +#include "camel-mbox-summary.h" + #include <errno.h> +#include <ctype.h> #include <netinet/in.h> -static CamelFolderSummaryClass *parent_class = NULL; +#define d(x) -static int count_messages (CamelFolderSummary *summary); -static int count_subfolders (CamelFolderSummary *summary); -static GPtrArray *get_subfolder_info (CamelFolderSummary *summary, - int first, int count); -static GPtrArray *get_message_info (CamelFolderSummary *summary, - int first, int count); -static void finalize (GtkObject *object); +#define CAMEL_MBOX_SUMMARY_VERSION 2 -static void -camel_mbox_summary_class_init (CamelMboxSummaryClass *camel_mbox_summary_class) +static int safe_write(int fd, char *buffer, size_t towrite); +static void camel_mbox_summary_add(CamelMboxSummary *s, CamelMboxMessageInfo *info); + +/* + Disk file format? + + message uid +message-block + date: + date received? + + subject: (unicode encoded) + from: (unicode encoded) + to: (unicode) + + content-block + +content-block + content-type: ; params; + content-id: + content-description: + content-transfer-encoding: + message-start: + header-size: + body-size: + + message-block + multipart-block + + */ + +/* pah, i dont care, its almost no code and it works, dont need a glist */ +struct _node { + struct _node *next; +}; + +static struct _node * +my_list_append(struct _node **list, struct _node *n) +{ + struct _node *ln = (struct _node *)list; + while (ln->next) + ln = ln->next; + n->next = 0; + ln->next = n; + return n; +} + +static int +my_list_size(struct _node **list) +{ + int len = 0; + struct _node *ln = (struct _node *)list; + while (ln->next) { + ln = ln->next; + len++; + } + return len; +} + +/* low-level io functions */ +static int +encode_int (FILE *out, gint32 value) +{ + int i; + + for (i=28;i>0;i-=7) { + if (value >= (1<<i)) { + unsigned int c = (value>>i) & 0x7f; + if (fputc(c, out) == -1) + return -1; + } + } + return fputc(value | 0x80, out); +} + +static gint32 +decode_int (FILE *in) { - GtkObjectClass *gtk_object_class = - GTK_OBJECT_CLASS (camel_mbox_summary_class); - CamelFolderSummaryClass *camel_folder_summary_class = - CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_class); + gint32 value=0, v; - parent_class = gtk_type_class (camel_folder_summary_get_type ()); + /* until we get the last byte, keep decoding 7 bits at a time */ + while ( ((v = fgetc(in)) & 0x80) == 0 && v!=EOF) { + value |= v; + value <<= 7; + } + value |= (v&0x7f); + return value; +} - /* virtual method override */ - camel_folder_summary_class->count_messages = count_messages; - camel_folder_summary_class->count_subfolders = count_subfolders; - camel_folder_summary_class->get_subfolder_info = get_subfolder_info; - camel_folder_summary_class->get_message_info = get_message_info; +static int +encode_fixed_int (FILE *out, gint32 value) +{ + guint32 save; - gtk_object_class->finalize = finalize; + save = htonl(value); + return fwrite(&save, sizeof(save), 1, out); } +static gint32 +decode_fixed_int (FILE *out) +{ + guint32 save; -GtkType -camel_mbox_summary_get_type (void) + if (fread(&save, sizeof(save), 1, out) != -1) { + return ntohl(save); + } else { + return -1; + } +} + +/* should be sorted, for binary search */ +/* This is a tokenisation mechanism for strings written to the + summary - to save space. + This list can have at most 31 words. */ +static char * tokens[] = { + "7bit", + "8bit", + "alternative", + "application", + "base64", + "boundary", + "charset", + "filename", + "html", + "image", + "iso-8859-1", + "iso-8859-8", + "message", + "mixed", + "multipart", + "name", + "octet-stream", + "parallel", + "plain", + "quoted-printable", + "rfc822", + "text", + "us-ascii", /* 23 words */ +}; + +#define tokens_len (sizeof(tokens)/sizeof(tokens[0])) + +/* baiscally ... + 0 = null + 1-tokens_len == tokens[id-1] + >=32 string, length = n-32 +*/ + +static int +encode_string (FILE *out, char *str) { - static GtkType camel_mbox_summary_type = 0; + if (str == NULL) { + return encode_int(out, 0); + } else { + int len = strlen(str); + int i, token=-1; + + if (len <= 16) { + char lower[32]; + + for (i=0;i<len;i++) + lower[i] = tolower(str[i]); + lower[i] = 0; + for (i=0;i<tokens_len;i++) { + if (!strcmp(tokens[i], lower)) { + token = i; + break; + } + } + } + if (token != -1) { + return encode_int(out, token+1); + } else { + if (encode_int(out, len+32) == -1) + return -1; + return fwrite(str, len, 1, out); + } + } + return 0; +} - if (!camel_mbox_summary_type) { - GtkTypeInfo camel_mbox_summary_info = - { - "CamelMboxSummary", - sizeof (CamelMboxSummary), - sizeof (CamelMboxSummaryClass), - (GtkClassInitFunc) camel_mbox_summary_class_init, - (GtkObjectInitFunc) NULL, - /* reserved_1 */ NULL, - /* reserved_2 */ NULL, - (GtkClassInitFunc) NULL, - }; +static char * +decode_string (FILE *in) +{ + char *ret; + int len; + + len = decode_int(in); - camel_mbox_summary_type = gtk_type_unique (camel_folder_summary_get_type (), &camel_mbox_summary_info); + if (len<32) { + if (len <= 0) { + ret = NULL; + } else if (len<= tokens_len) { + ret = g_strdup(tokens[len-1]); + } else { + g_warning("Invalid token encountered: %d", len); + ret = NULL; + } + } else if (len > 10240) { + g_warning("Got broken string header length: %d bytes", len); + ret = NULL; + } else { + len -= 32; + ret = g_malloc(len+1); + if (fread(ret, len, 1, in) == -1) { + g_free(ret); + return NULL; + } + ret[len]=0; } - return camel_mbox_summary_type; + return ret; } + + +/* allocation functions */ + static void -finalize (GtkObject *object) +body_part_dump(CamelMboxMessageContentInfo *bs, int depth) { - CamelMboxSummary *summary = CAMEL_MBOX_SUMMARY (object); - CamelMboxSummaryInformation *info; - int i; + CamelMboxMessageContentInfo *c; + char *prefix; - for (i = 0; i < summary->message_info->len; i++) { - info = &(((CamelMboxSummaryInformation *)summary->message_info->data)[i]); - g_free (info->headers.subject); - g_free (info->headers.sender); - g_free (info->headers.to); - g_free (info->headers.sent_date); - g_free (info->headers.received_date); - g_free (info->headers.uid); + if (bs == NULL) + return; + + prefix = alloca(depth*2+1); + memset(prefix, ' ', depth*2); + prefix[depth*2]=0; + printf("%scontent-range: %d %d %d\n", prefix, (int)bs->pos, (int)bs->bodypos, (int)bs->endpos); + printf("%scontent-type: %s/%s\n", prefix, bs->info.type?bs->info.type->type:"?", bs->info.type?bs->info.type->subtype:"?"); + printf("%scontent-id: %s\n", prefix, bs->info.id); + printf("%scontent-description: %s\n", prefix, bs->info.description); + printf("%scontent-transfer-encoding: %s\n", prefix, bs->info.encoding); + c = (CamelMboxMessageContentInfo *)bs->info.childs; + while (c) { + printf("%s -- \n", prefix); + body_part_dump(c, depth+1); + c = (CamelMboxMessageContentInfo *)c->info.next; } - g_array_free (summary->message_info, TRUE); +} - GTK_OBJECT_CLASS (parent_class)->finalize (object); -} +static void +message_struct_dump(CamelMboxMessageInfo *ms) +{ + char *tmp; -static int -count_messages (CamelFolderSummary *summary) + if (ms == NULL) { + printf("Empty message?\n"); + return; + } + + printf("Subject: %s\n", ms->info.subject); + printf("From: %s\n", ms->info.from); + printf("To: %s\n", ms->info.to); + tmp = header_format_date(ms->info.date_sent, 0); + printf("Date: %s\n", tmp); + g_free(tmp); + tmp = header_format_date(ms->info.date_received, 0); + printf("Date-Received: %s\n", tmp); + g_free(tmp); + printf("UID: %08x-%04x\n", atoi(ms->info.uid), ms->info.flags); + printf(" -- content ->\n"); + body_part_dump((CamelMboxMessageContentInfo *)ms->info.content, 1); +} + +static CamelMboxMessageContentInfo * +body_part_new(CamelMimeParser *mp, CamelMboxMessageContentInfo *parent, int start, int body) { - return CAMEL_MBOX_SUMMARY (summary)->nb_message; + CamelMboxMessageContentInfo *bs; + + bs = g_malloc0(sizeof(*bs)); + + bs->info.parent = (CamelMessageContentInfo *)parent; + + bs->info.type = camel_mime_parser_content_type(mp); + header_content_type_ref(bs->info.type); + + bs->info.id = header_msgid_decode(camel_mime_parser_header(mp, "content-id", NULL)); + bs->info.description = header_decode_string(camel_mime_parser_header(mp, "content-description", NULL)); + bs->info.encoding = header_content_encoding_decode(camel_mime_parser_header(mp, "content-transfer-encoding", NULL)); + + /* not sure what to set here? */ + bs->pos = start; + bs->bodypos = body; + bs->endpos = -1; + + if (parent) + my_list_append((struct _node **)&parent->info.childs, (struct _node *)bs); + + return bs; +} + +static CamelMboxMessageInfo * +message_struct_new(CamelMimeParser *mp, CamelMboxMessageContentInfo *parent, int start, int body, off_t xev_offset) +{ + CamelMboxMessageInfo *ms; + + ms = g_malloc0(sizeof(*ms)); + + /* FIXME: what about cc, sender vs from? */ + ms->info.subject = g_strdup(camel_mime_parser_header(mp, "subject", NULL)); + ms->info.from = g_strdup(camel_mime_parser_header(mp, "from", NULL)); + ms->info.to = g_strdup(camel_mime_parser_header(mp, "to", NULL)); + + ms->info.date_sent = header_decode_date(camel_mime_parser_header(mp, "date", NULL), NULL); + ms->info.date_received = 0; + + ms->info.content = (CamelMessageContentInfo *)body_part_new(mp, parent, start, body); + ms->xev_offset = xev_offset; + return ms; +} + +static void +body_part_free(CamelMboxMessageContentInfo *bs) +{ + CamelMboxMessageContentInfo *c, *cn; + + c = (CamelMboxMessageContentInfo *)bs->info.childs; + while (c) { + cn = (CamelMboxMessageContentInfo *)c->info.next; + body_part_free(c); + c = cn; + } + g_free(bs->info.id); + g_free(bs->info.description); + g_free(bs->info.encoding); + header_content_type_unref(bs->info.type); + g_free(bs); +} + +static void +message_struct_free(CamelMboxMessageInfo *ms) +{ + g_free(ms->info.subject); + g_free(ms->info.to); + g_free(ms->info.from); + body_part_free((CamelMboxMessageContentInfo *)ms->info.content); + g_free(ms); +} + + +/* IO functions */ +static CamelMboxMessageContentInfo * +body_part_load(FILE *in) +{ + CamelMboxMessageContentInfo *bs = NULL, *c; + struct _header_content_type *ct; + char *type; + char *subtype; + int i, count; + + d(printf("got content-block\n")); + bs = g_malloc0(sizeof(*bs)); + bs->pos = decode_int(in); + bs->bodypos = bs->pos + decode_int(in); + bs->endpos = bs->pos + decode_int(in); + + /* do content type */ + d(printf("got content-type\n")); + type = decode_string(in); + subtype = decode_string(in); + + ct = header_content_type_new(type, subtype); + bs->info.type = ct; + count = decode_int(in); + d(printf("getting %d params\n", count)); + for (i=0;i<count;i++) { + char *name = decode_string(in); + char *value = decode_string(in); + + d(printf(" %s = \"%s\"\n", name, value)); + + header_content_type_set_param(ct, name, value); + /* FIXME: do this so we dont have to double alloc/free */ + g_free(name); + g_free(value); + } + + d(printf("got content-id\n")); + bs->info.id = decode_string(in); + d(printf("got content-description\n")); + bs->info.description = decode_string(in); + d(printf("got content-encoding\n")); + bs->info.encoding = decode_string(in); + + count = decode_int(in); + d(printf("got children, %d\n", count)); + for (i=0;i<count;i++) { + c = body_part_load(in); + if (c) { + my_list_append((struct _node **)&bs->info.childs, (struct _node *)c); + c->info.parent = (CamelMessageContentInfo *)bs; + } else { + printf("Cannot load child\n"); + } + } + + return bs; } static int -count_subfolders (CamelFolderSummary *summary) +body_part_save(FILE *out, CamelMboxMessageContentInfo *bs) { - /* XXX */ - g_warning ("CamelMboxSummary::count_subfolders not implemented"); + CamelMboxMessageContentInfo *c, *cn; + struct _header_content_type *ct; + struct _header_param *hp; + + encode_int(out, bs->pos); + encode_int(out, bs->bodypos - bs->pos); + encode_int(out, bs->endpos - bs->pos); + + ct = bs->info.type; + if (ct) { + encode_string(out, ct->type); + encode_string(out, ct->subtype); + encode_int(out, my_list_size((struct _node **)&ct->params)); + hp = ct->params; + while (hp) { + encode_string(out, hp->name); + encode_string(out, hp->value); + hp = hp->next; + } + } else { + encode_string(out, NULL); + encode_string(out, NULL); + encode_int(out, 0); + } + encode_string(out, bs->info.id); + encode_string(out, bs->info.description); + encode_string(out, bs->info.encoding); + + encode_int(out, my_list_size((struct _node **)&bs->info.childs)); + + c = (CamelMboxMessageContentInfo *)bs->info.childs; + while (c) { + cn = (CamelMboxMessageContentInfo *)c->info.next; + body_part_save(out, c); + c = cn; + } + return 0; } -static GPtrArray * -get_subfolder_info (CamelFolderSummary *summary, int first, int count) +static CamelMboxMessageInfo * +message_struct_load(FILE *in) { - /* XXX */ - g_warning ("CamelMboxSummary::count_subfolders not implemented"); + CamelMboxMessageInfo *ms; + + ms = g_malloc0(sizeof(*ms)); + + ms->info.uid = g_strdup_printf("%u", decode_int(in)); + ms->info.flags = decode_int(in); + ms->info.date_sent = decode_int(in); + ms->info.date_received = decode_int(in); + ms->xev_offset = decode_int(in); + ms->info.subject = decode_string(in); + ms->info.from = decode_string(in); + ms->info.to = decode_string(in); + ms->info.content = (CamelMessageContentInfo *)body_part_load(in); + + return ms; +} + +static int +message_struct_save(FILE *out, CamelMboxMessageInfo *ms) +{ + encode_int(out, strtoul(ms->info.uid, NULL, 10)); + encode_int(out, ms->info.flags); + encode_int(out, ms->info.date_sent); + encode_int(out, ms->info.date_received); + encode_int(out, ms->xev_offset); + encode_string(out, ms->info.subject); + encode_string(out, ms->info.from); + encode_string(out, ms->info.to); + body_part_save(out, (CamelMboxMessageContentInfo *)ms->info.content); + return 0; } -static GPtrArray * -get_message_info (CamelFolderSummary *summary, int first, int count) +static unsigned int +header_evolution_decode(const char *in, unsigned int *uid, unsigned int *flags) { - CamelMboxSummary *mbox_summary = CAMEL_MBOX_SUMMARY (summary); - CamelMboxSummaryInformation *info; - GPtrArray *arr; + char *header; + if (in + && (header = header_decode_token(&in))) { + if (strlen(header) == strlen("00000000-0000") + && sscanf(header, "%08x-%04x", uid, flags) == 2) { + g_free(header); + return *uid; + } + g_free(header); + } - /* XXX bounds check */ + return ~0; +} + +static int +safe_write(int fd, char *buffer, size_t towrite) +{ + size_t donelen; + size_t len; - arr = g_ptr_array_new (); - for (; count; count--) { - info = &((CamelMboxSummaryInformation *)mbox_summary->message_info->data)[first++]; - g_ptr_array_add (arr, info); + donelen = 0; + while (donelen < towrite) { + len = write(fd, buffer + donelen, towrite - donelen); + if (len == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + donelen += len; } + return donelen; +} - return arr; -} - -/** - * camel_mbox_summary_save: - * @summary: - * @filename: - * @ex: - * - * save the summary into a file - **/ -void -camel_mbox_summary_save (CamelMboxSummary *summary, const gchar *filename, - CamelException *ex) -{ - CamelMboxSummaryInformation *msg_info; - guint cur_msg; - guint field_length; - gint fd; - gint write_result; /* XXX use this */ - guint32 data; - - fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, - S_IRUSR | S_IWUSR); - if (fd == -1) { - camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, - "could not create the mbox summary " - "file\n\t%s\nFull error is : %s\n", - filename, - strerror (errno)); - return; +static int +header_write(int fd, struct _header_raw *header, unsigned int uid, unsigned int flags) +{ + struct iovec iv[3]; + int outlen = 0; + + iv[1].iov_base = ":"; + iv[1].iov_len = 1; + + while (header) { + if (strcasecmp(header->name, "x-evolution")) { + int len; + + iv[0].iov_base = header->name; + iv[0].iov_len = strlen(header->name); + iv[2].iov_base = header->value; + iv[2].iov_len = strlen(header->value); + + do { + len = writev(fd, iv, 3); + } while (len == -1 && errno == EINTR); + + if (len == -1) + return -1; + outlen += len; + } + header = header->next; } - /* We write the file out in network byte order, not because - * that makes sense, but because it's easy. - */ - - data = htonl (CAMEL_MBOX_SUMMARY_VERSION); - write (fd, &data, sizeof (data)); - - data = htonl (summary->nb_message); - write (fd, &data, sizeof (data)); - data = htonl (summary->next_uid); - write (fd, &data, sizeof (data)); - data = htonl (summary->mbox_file_size); - write (fd, &data, sizeof (data)); - data = htonl (summary->mbox_modtime); - write (fd, &data, sizeof (data)); - - for (cur_msg = 0; cur_msg < summary->nb_message; cur_msg++) { - msg_info = (CamelMboxSummaryInformation *) - (summary->message_info->data) + cur_msg; - - /* Write meta-info. */ - data = htonl (msg_info->position); - write (fd, &data, sizeof (data)); - data = htonl (msg_info->size); - write (fd, &data, sizeof (data)); - data = htonl (msg_info->x_evolution_offset); - write (fd, &data, sizeof (data)); - data = htonl (msg_info->uid); - write (fd, &data, sizeof (data)); - write (fd, &msg_info->status, 1); - - /* Write subject. */ - if (msg_info->headers.subject) - field_length = strlen (msg_info->headers.subject); - else - field_length = 0; - data = htonl (field_length); - write (fd, &data, sizeof (data)); - if (msg_info->headers.subject) - write (fd, msg_info->headers.subject, field_length); - - /* Write sender. */ - if (msg_info->headers.sender) - field_length = strlen (msg_info->headers.sender); - else - field_length = 0; - data = htonl (field_length); - write (fd, &data, sizeof (data)); - if (msg_info->headers.sender) - write (fd, msg_info->headers.sender, field_length); - - /* Write to. */ - if (msg_info->headers.to) - field_length = strlen (msg_info->headers.to); - else - field_length = 0; - data = htonl (field_length); - write (fd, &data, sizeof (data)); - if (msg_info->headers.to) - write (fd, msg_info->headers.to, field_length); - - /* Write sent date. */ - if (msg_info->headers.sent_date) - field_length = strlen (msg_info->headers.sent_date); - else - field_length = 0; - data = htonl (field_length); - write (fd, &data, sizeof (data)); - if (msg_info->headers.sent_date) - write (fd, msg_info->headers.sent_date, field_length); - - /* Write received date. */ - if (msg_info->headers.received_date) - field_length = strlen (msg_info->headers.received_date); + return outlen; +} + +/* returns -1 on error, else number of bytes written */ +int +camel_mbox_summary_copy_block(int fromfd, int tofd, off_t readpos, size_t bytes) +{ + char buffer[4096]; + int written = 0; + off_t pos, newpos; + + pos = lseek(fromfd, 0, SEEK_CUR); + if (pos == -1) + return -1; + + newpos = lseek(fromfd, readpos, SEEK_SET); + if (newpos == -1 || newpos != readpos) + goto error; + + d(printf("oldpos = %d; copying %d from %d\n", (int)pos, (int)bytes, (int)readpos)); + + while (bytes>0) { + int toread, towrite, donelen; + + toread = bytes; + if (bytes>4096) + toread = 4096; else - field_length = 0; - data = htonl (field_length); - write (fd, &data, sizeof (data)); - if (msg_info->headers.received_date) - write (fd, msg_info->headers.received_date, field_length); + toread = bytes; + reread: + towrite = read(fromfd, buffer, toread); + if (towrite == -1) { + if (errno == EINTR || errno == EAGAIN) + goto reread; + goto error; + } + + /* check for 'end of file' */ + if (towrite == 0) + break; + + if ( (donelen = safe_write(tofd, buffer, towrite)) == -1) + goto error; + + written += donelen; + bytes -= donelen; + } + + d(printf("written %d bytes\n", written)); + + newpos = lseek(fromfd, pos, SEEK_SET); + if (newpos == -1 || newpos != pos); + return -1; + + return written; + +error: + lseek(fromfd, pos, SEEK_SET); + return -1; +} + +#define SAVEIT + +static int index_folder(CamelMboxSummary *s, int startoffset) +{ + CamelMimeParser *mp; + int fd; + int fdout; + int state; + + int toplevel = FALSE; + const char *xev; + char *data; + int datalen; + + int enc_id=-1; + int chr_id=-1; + int idx_id=-1; + struct _header_content_type *ct; + int doindex=FALSE; + CamelMimeFilterCharset *mfc = NULL; + CamelMimeFilterIndex *mfi = NULL; + CamelMimeFilterBasic *mf64 = NULL, *mfqp = NULL; + + CamelMboxMessageContentInfo *body = NULL, *parent = NULL; + CamelMboxMessageInfo *message = NULL; + + int from_end = 0; /* start of message */ + int from = 0; /* start of headers */ + int last_write = 0; /* last written position */ + int eof; + int write_offset = 0; /* how much does the dest differ from the source pos */ + int old_offset = 0; + + guint32 newuid; + off_t xevoffset = -1; + + char *tmpname; + + printf("indexing %s (%s) from %d\n", s->folder_path, s->summary_path, startoffset); + + fd = open(s->folder_path, O_RDONLY); + if (fd==-1) { + perror("Can't open folder"); + return -1; } - close (fd); -} - - - -/** - * camel_mbox_summary_load: - * @filename: - * @ex: - * - * load the summary from a file - * - * Return value: - **/ -CamelMboxSummary * -camel_mbox_summary_load (const gchar *filename, CamelException *ex) -{ - CamelMboxSummaryInformation *msg_info; - guint cur_msg; - guint field_length; - gint fd; - CamelMboxSummary *summary; - gint read_result; - guint32 data; - - fd = open (filename, O_RDONLY); - if (fd == -1) { - camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, - "could not open the mbox summary file\n" - "\t%s\nFull error is : %s\n", - filename, strerror (errno)); - return NULL; + tmpname = g_strdup_printf("%s.tmp", s->folder_path); + + fdout = open(tmpname, O_WRONLY|O_CREAT|O_TRUNC, 0600); + if (fdout==-1) { + perror("Can't open output"); + g_free(tmpname); + return -1; } - /* Verify version number. */ - read (fd, &data, sizeof(data)); - data = ntohl (data); - - if (data != CAMEL_MBOX_SUMMARY_VERSION) { - camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_SUMMARY_INVALID, - "This folder summary was written by " - "%s version of this software.", - data < CAMEL_MBOX_SUMMARY_VERSION ? - "an older" : "a newer"); - return NULL; + mp = camel_mime_parser_new(); + camel_mime_parser_init_with_fd(mp, fd); + camel_mime_parser_scan_from(mp, TRUE); + + /* FIXME: cleaner fail code */ + if (startoffset > 0) { + if (camel_mime_parser_seek(mp, startoffset, SEEK_SET) != startoffset) { + g_free(tmpname); + gtk_object_unref((GtkObject *)mp); + return -1; + } } - summary = CAMEL_MBOX_SUMMARY (gtk_object_new (camel_mbox_summary_get_type (), NULL)); - - read (fd, &data, sizeof(data)); - summary->nb_message = ntohl (data); - read (fd, &data, sizeof(data)); - summary->next_uid = ntohl (data); - read (fd, &data, sizeof(data)); - summary->mbox_file_size = ntohl (data); - read (fd, &data, sizeof(data)); - summary->mbox_modtime = ntohl (data); - - summary->message_info = - g_array_new (FALSE, FALSE, - sizeof (CamelMboxSummaryInformation)); - g_array_set_size (summary->message_info, summary->nb_message); - - for (cur_msg = 0; cur_msg < summary->nb_message; cur_msg++) { - msg_info = (CamelMboxSummaryInformation *) - (summary->message_info->data) + cur_msg; - - /* Read the meta-info. */ - read (fd, &data, sizeof(data)); - msg_info->position = ntohl (data); - read (fd, &data, sizeof(data)); - msg_info->size = ntohl (data); - read (fd, &data, sizeof(data)); - msg_info->x_evolution_offset = ntohl (data); - read (fd, &data, sizeof(data)); - msg_info->uid = ntohl (data); - msg_info->headers.uid = g_strdup_printf ("%d", msg_info->uid); - read (fd, &msg_info->status, 1); - - /* Read the subject. */ - read (fd, &field_length, sizeof (field_length)); - field_length = ntohl (field_length); - if (field_length > 0) { - msg_info->headers.subject = - g_new0 (gchar, field_length + 1); - read (fd, msg_info->headers.subject, field_length); - } else - msg_info->headers.subject = NULL; - - /* Read the sender. */ - read (fd, &field_length, sizeof (field_length)); - field_length = ntohl (field_length); - if (field_length > 0) { - msg_info->headers.sender = - g_new0 (gchar, field_length + 1); - read (fd, msg_info->headers.sender, field_length); - } else - msg_info->headers.sender = NULL; - - /* Read the "to" field. */ - read (fd, &field_length, sizeof (field_length)); - field_length = ntohl (field_length); - if (field_length > 0) { - msg_info->headers.to = - g_new0 (gchar, field_length + 1); - read (fd, msg_info->headers.to, field_length); - } else - msg_info->headers.to = NULL; - - /* Read the sent date field. */ - read (fd, &field_length, sizeof (field_length)); - field_length = ntohl (field_length); - if (field_length > 0) { - msg_info->headers.sent_date = - g_new0 (gchar, field_length + 1); - read (fd, msg_info->headers.sent_date, field_length); - } else - msg_info->headers.sent_date = NULL; - - /* Read the received date field. */ - read (fd, &field_length, sizeof (field_length)); - field_length = ntohl (field_length); - if (field_length > 0) { - msg_info->headers.received_date = - g_new0 (gchar, field_length + 1); - read (fd, msg_info->headers.received_date, - field_length); - } else - msg_info->headers.received_date = NULL; - } - - close (fd); - return summary; -} - - -/** - * camel_mbox_summary_append_entries: - * @summary: - * @entries: - * - * append an entry to a summary - **/ -void -camel_mbox_summary_append_entries (CamelMboxSummary *summary, GArray *entries) -{ - - summary->message_info = g_array_append_vals (summary->message_info, - entries->data, - entries->len); + mfi = camel_mime_filter_index_new_ibex(s->index); + + while ( (state = camel_mime_parser_step(mp, &data, &datalen)) != HSCAN_EOF ) { + switch(state) { + case HSCAN_FROM: /* starting a new message content */ + /* save the current position */ + d(printf("from = %d\n", (int)camel_mime_parser_tell(mp))); + toplevel = FALSE; + from = camel_mime_parser_tell(mp); + break; + + case HSCAN_FROM_END: + d(printf("from-end = %d\n", (int)camel_mime_parser_tell(mp))); + d(printf("message from %d to %d\n", from_end, (int)camel_mime_parser_tell(mp))); + from_end = camel_mime_parser_tell(mp); + break; + + case HSCAN_MESSAGE: + case HSCAN_MULTIPART: + case HSCAN_HEADER: /* starting a new header */ + newuid=~0; + if (!toplevel) { + char name[32]; + unsigned int olduid, oldflags; + int headerlen; + int docopy = FALSE; + + /* check for X-Evolution header ... if its there, nothing to do (skip content) */ + xev = camel_mime_parser_header(mp, "x-evolution", &xevoffset); + if (xev) { + d(printf("An x-evolution header exists at: %d = %s\n", xevoffset + write_offset, xev)); + xevoffset = xevoffset + write_offset; + if (header_evolution_decode(xev, &olduid, &oldflags) != ~0) { + d(printf(" uid = %d = %x\n", olduid, olduid)); + newuid = olduid; +#if 0 + while (camel_mime_parser_step(mp, &data, &datalen) != HSCAN_FROM_END) + ; + break; +#endif + } else { + printf("Invalid xev header? I need to write out a new one ...\n"); + } + } + + toplevel = TRUE; + + /* assign a new uid for this message */ + if (newuid == ~0) { + newuid = s->nextuid++; + docopy = TRUE; + } else { + /* make sure we account for this uid when assigning uid's */ + /* this really needs a pre-scan pass ... *sigh* */ + camel_mbox_summary_set_uid(s, newuid); + } + + /* setup index name for this uid */ + sprintf(name, "%x", newuid); + camel_mime_filter_index_set_name(mfi, name); + /* remove all references to this name from the index */ + if (s->index) + ibex_unindex(s->index, name); + + d(printf("Message content starts at %d\n", camel_mime_parser_tell(mp))); + + if (docopy) { + /* now, copy over bits of mbox from last write, and insert the X-Evolution header (at the top of headers) */ + /* if we already have a valid x-evolution header, use that, dont need to copy */ + camel_mbox_summary_copy_block(fd, fdout, last_write, from-last_write); + last_write = from; + + headerlen = header_write(fdout, camel_mime_parser_headers_raw(mp), newuid, 0); + sprintf(name, "X-Evolution: %08x-%04x\n\n", newuid, 0); + safe_write(fdout, name, strlen(name)); + d(printf("new X-Evolution at %d\n", headerlen + from + write_offset)); + xevoffset = headerlen + from + write_offset; + old_offset = write_offset; + + write_offset += (headerlen - (camel_mime_parser_tell(mp)-from)) + strlen(name); + last_write = camel_mime_parser_tell(mp); + } + } else { + old_offset = write_offset; + } + + /* we only care about the rest for actual content parts */ + /* TODO: Cleanup, this is a huge mess */ + if (state != HSCAN_HEADER) { + if (message == NULL) { + message = message_struct_new(mp, parent, camel_mime_parser_tell_start_headers(mp)+old_offset, camel_mime_parser_tell(mp)+write_offset, xevoffset); + parent = (CamelMboxMessageContentInfo *)message->info.content; + if (newuid != ~0) { + message->info.uid = g_strdup_printf("%u", newuid); + } else { + g_warning("This shouldn't happen?"); + } + } else { + parent = body_part_new(mp, parent, camel_mime_parser_tell_start_headers(mp)+old_offset, camel_mime_parser_tell(mp)+write_offset); + } + break; + } + + if (message == NULL) { + message = message_struct_new(mp, parent, camel_mime_parser_tell_start_headers(mp)+old_offset, camel_mime_parser_tell(mp)+write_offset, xevoffset); + body = (CamelMboxMessageContentInfo *)message->info.content; + if (newuid != ~0) { + message->info.uid = g_strdup_printf("%u", newuid); + } else { + g_warning("This shouldn't happen?"); + } + } else { + body = body_part_new(mp, parent, camel_mime_parser_tell_start_headers(mp)+old_offset, camel_mime_parser_tell(mp)+write_offset); + } + + /* check headers for types that we can index */ + ct = camel_mime_parser_content_type(mp); + if (header_content_type_is(ct, "text", "*")) { + char *encoding; + const char *charset; + + /* TODO: The filters should all be cached, so they aren't recreated between + messages/message parts */ + encoding = header_content_encoding_decode(camel_mime_parser_header(mp, "content-transfer-encoding", NULL)); + if (encoding) { + if (!strcasecmp(encoding, "base64")) { + d(printf("Adding decoding filter for base64\n")); + if (mf64 == NULL) + mf64 = camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_BASE64_DEC); + enc_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)mf64); + } else if (!strcasecmp(encoding, "quoted-printable")) { + d(printf("Adding decoding filter for quoted-printable\n")); + if (mfqp == NULL) + mfqp = camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_QP_DEC); + enc_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)mfqp); + } + g_free(encoding); + } + + charset = header_content_type_param(ct, "charset"); + if (charset!=NULL + && !(strcasecmp(charset, "us-ascii")==0 + || strcasecmp(charset, "utf-8")==0)) { + d(printf("Adding conversion filter from %s to utf-8\n", charset)); + if (mfc == NULL) + mfc = camel_mime_filter_charset_new_convert(charset, "utf-8"); + if (mfc) { + chr_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)mfc); + } else { + g_warning("Cannot convert '%s' to 'utf-8', message display may be corrupt", charset); + } + } + + doindex = TRUE; + + /* and this filter actually does the indexing */ + idx_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)mfi); + } else { + doindex = FALSE; + } + break; + + /* fixme, this needs thought *sigh* */ + case HSCAN_MESSAGE_END: + case HSCAN_MULTIPART_END: + if (parent) { + parent->endpos = camel_mime_parser_tell(mp)+write_offset; + if (parent->info.parent == NULL) { + camel_mbox_summary_add(s, message); + message = NULL; + parent = NULL; + } else { + parent = (CamelMboxMessageContentInfo *)parent->info.parent; + } + } + break; + + case HSCAN_BODY: + if (doindex) { + d(printf("Got content to index:\n%.*s", datalen, data)); + } + break; + + case HSCAN_BODY_END: + if (body) { + body->endpos = camel_mime_parser_tell(mp)+write_offset; + if (body->info.parent == NULL) { + camel_mbox_summary_add(s, message); + message = NULL; + } + } + + d(printf("end of content, removing decoders\n")); + if (enc_id != -1) { + camel_mime_parser_filter_remove(mp, enc_id); + enc_id = -1; + } + if (chr_id != -1) { + camel_mime_parser_filter_remove(mp, chr_id); + chr_id = -1; + } + if (idx_id != -1) { + camel_mime_parser_filter_remove(mp, idx_id); + idx_id = -1; + } + break; + } + } + + /* did we actually write anything out? Then rename and be done with it. */ + if (last_write>0) { + eof = camel_mime_parser_tell(mp); + camel_mbox_summary_copy_block(fd, fdout, last_write, eof-last_write); + + if (close(fdout) == -1) { + perror("Could not close output file"); + unlink(tmpname); + } else { + printf("renaming %s to %s\n", tmpname, s->folder_path); + if (rename(tmpname, s->folder_path) == -1) { + perror("Error renaming file"); + unlink(tmpname); + } + } + } else { + /* no, then dont bother touching the inbox */ + printf("No written changes to mbox, removing tmp file\n"); + close(fdout); + unlink(tmpname); + } + + close(fd); + + if (mf64) gtk_object_unref((GtkObject *)mf64); + if (mfqp) gtk_object_unref((GtkObject *)mfqp); + if (mfc) gtk_object_unref((GtkObject *)mfc); + if (mfi) gtk_object_unref((GtkObject *)mfi); + + /* force an index sync? */ + if (s->index) { + ibex_write(s->index); + } + + gtk_object_unref((GtkObject *)mp); + + /* and finally ... update the summary sync info */ + { + struct stat st; + + if (stat(s->folder_path, &st) == 0) { + s->time = st.st_mtime; + s->size = st.st_size; + } + } + + g_free(tmpname); + + return 0; +} + +CamelMboxSummary *camel_mbox_summary_new(const char *summary, const char *folder, ibex *index) +{ + CamelMboxSummary *s; + + s = g_malloc0(sizeof(*s)); + + s->dirty = TRUE; + s->folder_path = g_strdup(folder); + s->summary_path = g_strdup(summary); + /* FIXME: refcount index? */ + s->index = index; + + s->messages = g_ptr_array_new(); + s->message_uid = g_hash_table_new(g_str_hash, g_str_equal); + + /* always force an update */ + s->time = 0; + s->size = 0; + + s->nextuid = 1; + + /* TODO: force an initial load right now? */ + + return s; +} + +void camel_mbox_summary_unref(CamelMboxSummary *s) +{ + g_warning("Unimplemented function, mbox_summary_unref"); +} + +/* check that the summary is uptodate, TRUE means it is uptodate */ +int camel_mbox_summary_check(CamelMboxSummary *s) +{ + struct stat st; + + /* no folder at all? */ + if (stat(s->folder_path, &st) != 0) + return FALSE; + + return (st.st_size == s->size) && (st.st_mtime == s->time); +} + +static void camel_mbox_summary_add(CamelMboxSummary *s, CamelMboxMessageInfo *info) +{ + if (info->info.uid == NULL) { + info->info.uid = g_strdup_printf("%u", s->nextuid++); + } + if (g_hash_table_lookup(s->message_uid, info->info.uid)) { + g_error("Trying to insert message with clashing uid's"); + } + d(printf("adding %s\n", info->info.uid)); + g_ptr_array_add(s->messages, info); + g_hash_table_insert(s->message_uid, info->info.uid, info); + s->dirty = TRUE; +} + +static int summary_header_read(FILE *fp, guint32 *version, time_t *time, size_t *size, guint32 *nextuid) +{ + fseek(fp, 0, SEEK_SET); + *version = decode_fixed_int(fp); + *time = decode_fixed_int(fp); + *size = decode_fixed_int(fp); + *nextuid = decode_fixed_int(fp); + return ferror(fp); +} + +static void +summary_clear(CamelMboxSummary *s) +{ + int i; + + for (i=0;i<s->messages->len;i++) { + message_struct_free(g_ptr_array_index(s->messages, i)); + } + g_ptr_array_free(s->messages, TRUE); + g_hash_table_destroy(s->message_uid); + + s->messages = g_ptr_array_new(); + s->message_uid = g_hash_table_new(g_str_hash, g_str_equal); +} + +int camel_mbox_summary_load(CamelMboxSummary *s) +{ + struct stat st; + FILE *fp; + int i, total; + CamelMboxMessageInfo *info; + + summary_clear(s); + + if ((fp = fopen(s->summary_path, "r")) == NULL) { + g_warning("Loading non-existant summary, generating summary for folder: %s: %s", s->summary_path, strerror(errno)); + index_folder(s, 0); + camel_mbox_summary_save(s); + } else { + guint32 version, nextuid; + time_t time; + size_t size; + + if (stat(s->folder_path, &st) != 0) { + g_warning("Uh, no folder anyway, aborting"); + fclose(fp); + return -1; + } + + if (summary_header_read(fp, &version, &time, &size, &nextuid) != 0 + || version != CAMEL_MBOX_SUMMARY_VERSION) { + g_warning("Summary missing or version mismatch, reloading summary"); + fclose(fp); + index_folder(s, 0); + camel_mbox_summary_save(s); + return 0; + } + + s->nextuid = MAX(s->nextuid, nextuid); + s->time = time; + s->size = size; + total = decode_fixed_int(fp); + + if (time != st.st_mtime || size != st.st_size) { + /* if its grown, then just index the new stuff, and load the rest from the summary */ + if (size < st.st_size) { + g_warning("Indexing/summarizing from start position: %d", size); + + d(printf("loading %d items from summary file\n", total)); + for (i=0;i<total;i++) { + info = message_struct_load(fp); + if (info) { + camel_mbox_summary_add(s, info); + } else { + break; + } + } + fclose(fp); + s->dirty = FALSE; + index_folder(s, size); /* if it adds any, it'll dirtify it */ + camel_mbox_summary_save(s); + } else { + g_warning("Folder changed/smaller, reindexing everything"); + index_folder(s, 0); + camel_mbox_summary_save(s); + fclose(fp); + } + return 0; + } + + printf("loading %d items from summary file\n", total); + for (i=0;i<total;i++) { + info = message_struct_load(fp); + if (info) { + camel_mbox_summary_add(s, info); + } else { + break; + } + } + fclose(fp); + s->dirty = FALSE; + } + return 0; +} + +static int summary_header_write(FILE *fp, CamelMboxSummary *s) +{ + fseek(fp, 0, SEEK_SET); + encode_fixed_int(fp, CAMEL_MBOX_SUMMARY_VERSION); + encode_fixed_int(fp, s->time); + /* if we're dirty, then dont *really* save it ... */ + if (s->dirty) + encode_fixed_int(fp, 0); + else + encode_fixed_int(fp, s->size); + encode_fixed_int(fp, s->nextuid); + fflush(fp); + return ferror(fp); +} + +static int summary_header_save(CamelMboxSummary *s) +{ + int fd; + FILE *fp; + + fd = open(s->summary_path, O_WRONLY|O_CREAT, 0600); + if (fd == -1) + return -1; + fp = fdopen(fd, "w"); + if (fp == NULL) + return -1; + + summary_header_write(fp, s); + return fclose(fp); +} + +int camel_mbox_summary_save(CamelMboxSummary *s) +{ + int i, fd; + FILE *fp; + + printf("saving summary? %s\n", s->summary_path); + + /* FIXME: error checking */ + if (s->dirty) { + printf("yes\n"); + fd = open(s->summary_path, O_WRONLY|O_CREAT|O_TRUNC, 0600); + if (fd == -1) + return -1; + fp = fdopen(fd, "w"); + if (fp == NULL) + return -1; + + s->dirty = FALSE; + + summary_header_write(fp, s); + encode_fixed_int(fp, s->messages->len); + + printf("message count = %d\n", s->messages->len); + + for (i=0;i<s->messages->len;i++) { + message_struct_save(fp, g_ptr_array_index(s->messages, i)); + } + fclose(fp); + } else { + printf("no\n"); + } + return 0; +} + +CamelMboxMessageInfo *camel_mbox_summary_uid(CamelMboxSummary *s, const char *uid) +{ + return g_hash_table_lookup(s->message_uid, uid); +} + +CamelMboxMessageInfo *camel_mbox_summary_index(CamelMboxSummary *s, int index) +{ + return g_ptr_array_index(s->messages, index); +} + +int camel_mbox_summary_message_count(CamelMboxSummary *s) +{ + return s->messages->len; +} + +guint32 camel_mbox_summary_next_uid(CamelMboxSummary *s) +{ + guint32 uid = s->nextuid++; + + summary_header_save(s); + return uid; +} + +guint32 camel_mbox_summary_set_uid(CamelMboxSummary *s, guint32 uid) +{ + if (s->nextuid < uid) { + s->nextuid = uid; + summary_header_save(s); + } + return s->nextuid; +} + +#if 0 +int main(int argc, char **argv) +{ + if (argc<2) { + printf("usage: %s mbox\n", argv[0]); + return 1; + } + + gtk_init(&argc, &argv); + + index_folder(argv[1]); + + return 0; } +#endif diff --git a/camel/providers/mbox/camel-mbox-summary.h b/camel/providers/mbox/camel-mbox-summary.h index f8570f15c0..80b59ef54f 100644 --- a/camel/providers/mbox/camel-mbox-summary.h +++ b/camel/providers/mbox/camel-mbox-summary.h @@ -1,85 +1,78 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ - -/* - * - * Author : Bertrand Guiheneuf <bertrand@helixcode.com> +/* + * Copyright (C) 2000 Helix Code Inc. * - * Copyright (C) 1999 Helix Code (http://www.helixcode.com). + * Authors: Michael Zucchi <notzed@helixcode.com> * - * 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 free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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. + * 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 Library 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 + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#ifndef MBOX_SUMMARY_H -#define MBOX_SUMMARY_H 1 - -#include <camel-folder-summary.h> - -#define CAMEL_MBOX_SUMMARY_TYPE (camel_mbox_summary_get_type ()) -#define CAMEL_MBOX_SUMMARY(obj) (GTK_CHECK_CAST((obj), CAMEL_MBOX_SUMMARY_TYPE, CamelMboxSummary)) -#define CAMEL_MBOX_SUMMARY_CLASS(k) (GTK_CHECK_CLASS_CAST ((k), CAMEL_MBOX_SUMMARY_TYPE, CamelMboxSummaryClass)) -#define CAMEL_IS_MBOX_SUMMARY(o) (GTK_CHECK_TYPE((o), CAMEL_MBOX_SUMMARY_TYPE)) - - -#define CAMEL_MBOX_SUMMARY_VERSION 1 +#ifndef _CAMEL_MBOX_SUMMARY_H +#define _CAMEL_MBOX_SUMMARY_H +#include <glib.h> +#include <camel/camel-folder.h> +#include <libibex/ibex.h> typedef struct { - CamelMessageInfo headers; + CamelMessageContentInfo info; - guint32 position; - guint size; - guint x_evolution_offset; - guint32 uid; - guchar status; + /* position in stream of this part */ + off_t pos; + off_t bodypos; + off_t endpos; +} CamelMboxMessageContentInfo; -} CamelMboxSummaryInformation; - - -/* this contains informations about the whole mbox file */ typedef struct { - CamelFolderSummary parent_object; + CamelMessageInfo info; - guint nb_message; /* number of messages in the summary */ - guint32 next_uid; - guint32 mbox_file_size; - guint32 mbox_modtime; + /* position of the xev header, if one exists */ + off_t xev_offset; +} CamelMboxMessageInfo; - GArray *message_info; /* array of CamelMboxSummaryInformation */ +typedef struct { + int dirty; /* if anything has changed */ -} CamelMboxSummary; + char *folder_path; + char *summary_path; + ibex *index; -typedef struct { - CamelFolderSummaryClass parent_class; + GPtrArray *messages; /* array of messages matching mbox order */ + GHashTable *message_uid; /* index to messages by uid */ -} CamelMboxSummaryClass; + int nextuid; + time_t time; /* time/size of folder's last update */ + size_t size; +} CamelMboxSummary; -GtkType camel_mbox_summary_get_type (void); +CamelMboxSummary *camel_mbox_summary_new(const char *summary, const char *folder, ibex *index); +void camel_mbox_summary_unref(CamelMboxSummary *); -void camel_mbox_summary_save (CamelMboxSummary *summary, - const gchar *filename, CamelException *ex); -CamelMboxSummary *camel_mbox_summary_load (const gchar *filename, - CamelException *ex); +int camel_mbox_summary_load(CamelMboxSummary *); +int camel_mbox_summary_save(CamelMboxSummary *); +int camel_mbox_summary_check(CamelMboxSummary *); -gboolean camel_mbox_summary_check_sync (gchar *summary_filename, - gchar *mbox_filename, - CamelException *ex); +guint32 camel_mbox_summary_next_uid(CamelMboxSummary *); +/* set the minimum uid */ +guint32 camel_mbox_summary_set_uid(CamelMboxSummary *s, guint32 uid); -void camel_mbox_summary_append_entries (CamelMboxSummary *summary, - GArray *entries); +CamelMboxMessageInfo *camel_mbox_summary_uid(CamelMboxSummary *s, const char *uid); +CamelMboxMessageInfo *camel_mbox_summary_index(CamelMboxSummary *, int index); +int camel_mbox_summary_message_count(CamelMboxSummary *); +/* TODO: should be in a utility library */ +int camel_mbox_summary_copy_block(int fromfd, int tofd, off_t readpos, size_t bytes); -#endif /* MBOX_SUMMARY_H */ +#endif /* ! _CAMEL_MBOX_SUMMARY_H */ diff --git a/camel/providers/mbox/camel-mbox-utils.c b/camel/providers/mbox/camel-mbox-utils.c index cd7da089e9..31710468b6 100644 --- a/camel/providers/mbox/camel-mbox-utils.c +++ b/camel/providers/mbox/camel-mbox-utils.c @@ -520,55 +520,3 @@ camel_mbox_write_xev (CamelMboxFolder *folder, g_free (tmp_file_name_secure); return next_free_uid; } - - - - - - -GArray * -parsed_information_to_mbox_summary (GArray *parsed_information) -{ - guint cur_msg; - CamelMboxParserMessageInfo *cur_msg_info; - GArray *mbox_summary; - CamelMboxSummaryInformation *cur_sum_info; - - mbox_summary = g_array_new (FALSE, FALSE, sizeof (CamelMboxSummaryInformation)); - mbox_summary = g_array_set_size (mbox_summary, parsed_information->len); - - for (cur_msg = 0; cur_msg < parsed_information->len; cur_msg++) { - - cur_msg_info = (CamelMboxParserMessageInfo *)(parsed_information->data) + cur_msg; - cur_sum_info = (CamelMboxSummaryInformation *)(mbox_summary->data) + cur_msg; - - cur_sum_info->position = cur_msg_info->message_position; - - cur_sum_info->size = cur_msg_info->size; - - cur_sum_info->x_evolution_offset = cur_msg_info->x_evolution_offset; - - cur_sum_info->uid = cur_msg_info->uid; - cur_sum_info->headers.uid = g_strdup_printf ("%d", - cur_sum_info->uid); - - cur_sum_info->status = cur_msg_info->status; - - cur_sum_info->headers.subject = cur_msg_info->subject; - cur_msg_info->subject = NULL; - - cur_sum_info->headers.sender = cur_msg_info->from; - cur_msg_info->from = NULL; - - cur_sum_info->headers.to = cur_msg_info->to; - cur_msg_info->to = NULL; - - /* XXX I'm guessing one of these is wrong. */ - cur_sum_info->headers.received_date = cur_msg_info->date; - cur_sum_info->headers.sent_date = g_strdup (cur_msg_info->date); - cur_msg_info->date = NULL; - - } - - return mbox_summary; -} |