2013-02-17 [colin] 3.9.0cvs75
authorColin Leroy <colin@colino.net>
Sun, 17 Feb 2013 21:22:03 +0000 (21:22 +0000)
committerColin Leroy <colin@colino.net>
Sun, 17 Feb 2013 21:22:03 +0000 (21:22 +0000)
* src/plugins/Makefile.am
* src/plugins/archive/Makefile.am
* src/plugins/clamd/Makefile.am
* src/plugins/clamd/clamav_plugin.c
* src/plugins/clamd/clamav_plugin.h
* src/plugins/clamd/clamav_plugin_gtk.c
* src/plugins/clamd/placeholder.txt
* src/plugins/clamd/libclamd/Makefile.am
* src/plugins/clamd/libclamd/clamd-plugin.c
* src/plugins/clamd/libclamd/clamd-plugin.h
* src/plugins/fetchinfo/Makefile.am
* src/plugins/fetchinfo/fetchinfo_plugin.c
* src/plugins/fetchinfo/fetchinfo_plugin.h
* src/plugins/fetchinfo/fetchinfo_plugin_gtk.c
* src/plugins/fetchinfo/placeholder.txt
* src/plugins/gdata/Makefile.am
* src/plugins/gdata/cm_gdata_contacts.c
* src/plugins/gdata/cm_gdata_contacts.h
* src/plugins/gdata/cm_gdata_prefs.c
* src/plugins/gdata/cm_gdata_prefs.h
* src/plugins/gdata/gdata_plugin.c
* src/plugins/gdata/gdata_plugin.h
* src/plugins/gdata/placeholder.txt
* src/plugins/geolocation/placeholder.txt
* src/plugins/gtkhtml2_viewer/placeholder.txt
* src/plugins/mailmbox/Makefile.am
* src/plugins/mailmbox/carray.c
* src/plugins/mailmbox/carray.h
* src/plugins/mailmbox/chash.c
* src/plugins/mailmbox/chash.h
* src/plugins/mailmbox/clist.c
* src/plugins/mailmbox/clist.h
* src/plugins/mailmbox/mailimf.c
* src/plugins/mailmbox/mailimf.h
* src/plugins/mailmbox/mailimf_types.c
* src/plugins/mailmbox/mailimf_types.h
* src/plugins/mailmbox/mailimf_types_helper.c
* src/plugins/mailmbox/mailimf_types_helper.h
* src/plugins/mailmbox/mailimf_write.c
* src/plugins/mailmbox/mailimf_write.h
* src/plugins/mailmbox/maillock.c
* src/plugins/mailmbox/maillock.h
* src/plugins/mailmbox/mailmbox.c
* src/plugins/mailmbox/mailmbox.h
* src/plugins/mailmbox/mailmbox_folder.c
* src/plugins/mailmbox/mailmbox_folder.h
* src/plugins/mailmbox/mailmbox_gtk.deps
* src/plugins/mailmbox/mailmbox_parse.c
* src/plugins/mailmbox/mailmbox_parse.h
* src/plugins/mailmbox/mailmbox_types.c
* src/plugins/mailmbox/mailmbox_types.h
* src/plugins/mailmbox/mmapstring.c
* src/plugins/mailmbox/mmapstring.h
* src/plugins/mailmbox/placeholder.txt
* src/plugins/mailmbox/plugin.c
* src/plugins/mailmbox/plugin_gtk.c
* src/plugins/mailmbox/plugin_gtk.h
* src/plugins/newmail/Makefile.am
* src/plugins/newmail/newmail.c
* src/plugins/newmail/placeholder.txt
* src/plugins/notification/Makefile.am
* src/plugins/notification/claws.def
* src/plugins/notification/notification_banner.c
* src/plugins/notification/notification_banner.h
* src/plugins/notification/notification_command.c
* src/plugins/notification/notification_command.h
* src/plugins/notification/notification_core.c
* src/plugins/notification/notification_core.h
* src/plugins/notification/notification_foldercheck.c
* src/plugins/notification/notification_foldercheck.h
* src/plugins/notification/notification_hotkeys.c
* src/plugins/notification/notification_hotkeys.h
* src/plugins/notification/notification_indicator.c
* src/plugins/notification/notification_indicator.h
* src/plugins/notification/notification_lcdproc.c
* src/plugins/notification/notification_lcdproc.h
* src/plugins/notification/notification_pixbuf.c
* src/plugins/notification/notification_pixbuf.h
* src/plugins/notification/notification_plugin.c
* src/plugins/notification/notification_plugin.h
* src/plugins/notification/notification_popup.c
* src/plugins/notification/notification_popup.h
* src/plugins/notification/notification_prefs.c
* src/plugins/notification/notification_prefs.h
* src/plugins/notification/notification_trayicon.c
* src/plugins/notification/notification_trayicon.h
* src/plugins/notification/placeholder.txt
* src/plugins/notification/plugin.def
* src/plugins/notification/raw_claws_mail_logo_64x64.h
* src/plugins/notification/version.rc
* src/plugins/pdf_viewer/Makefile.am
* src/plugins/pdf_viewer/doc_index.xpm
* src/plugins/pdf_viewer/doc_index_close.xpm
* src/plugins/pdf_viewer/doc_info.xpm
* src/plugins/pdf_viewer/first_arrow.xpm
* src/plugins/pdf_viewer/last_arrow.xpm
* src/plugins/pdf_viewer/left_arrow.xpm
* src/plugins/pdf_viewer/placeholder.txt
* src/plugins/pdf_viewer/poppler_viewer.c
* src/plugins/pdf_viewer/poppler_viewer.h
* src/plugins/pdf_viewer/right_arrow.xpm
* src/plugins/pdf_viewer/rotate_left.xpm
* src/plugins/pdf_viewer/rotate_right.xpm
* src/plugins/pdf_viewer/zoom_fit.xpm
* src/plugins/pdf_viewer/zoom_in.xpm
* src/plugins/pdf_viewer/zoom_out.xpm
* src/plugins/pdf_viewer/zoom_width.xpm
* src/plugins/perl/Makefile.am
* src/plugins/perl/perl_gtk.c
* src/plugins/perl/perl_gtk.h
* src/plugins/perl/perl_plugin.c
* src/plugins/perl/perl_plugin.h
* src/plugins/perl/placeholder.txt
* src/plugins/python/Makefile.am
* src/plugins/python/clawsmailmodule.c
* src/plugins/python/clawsmailmodule.h
* src/plugins/python/composewindowtype.c
* src/plugins/python/composewindowtype.h
* src/plugins/python/foldertype.c
* src/plugins/python/foldertype.h
* src/plugins/python/messageinfotype.c
* src/plugins/python/messageinfotype.h
* src/plugins/python/nodetype.c
* src/plugins/python/nodetype.h
* src/plugins/python/placeholder.txt
* src/plugins/python/python-hooks.c
* src/plugins/python/python-hooks.h
* src/plugins/python/python-shell.c
* src/plugins/python/python-shell.h
* src/plugins/python/python_plugin.c
* src/plugins/vcalendar/Makefile.in
Add some plugins (clamd, fetchinfo, gdata, mailmbox, newmail,
notification, pdf_viewer, perl, python). Notification not yet
enabled because it has too much autoconf switches for my taste.

134 files changed:
ChangeLog
PATCHSETS
configure.ac
src/plugins/Makefile.am
src/plugins/archive/Makefile.am
src/plugins/clamd/Makefile.am [new file with mode: 0644]
src/plugins/clamd/clamav_plugin.c [new file with mode: 0644]
src/plugins/clamd/clamav_plugin.h [new file with mode: 0644]
src/plugins/clamd/clamav_plugin_gtk.c [new file with mode: 0644]
src/plugins/clamd/libclamd/Makefile.am [new file with mode: 0644]
src/plugins/clamd/libclamd/clamd-plugin.c [new file with mode: 0644]
src/plugins/clamd/libclamd/clamd-plugin.h [new file with mode: 0644]
src/plugins/clamd/placeholder.txt [deleted file]
src/plugins/fetchinfo/Makefile.am [new file with mode: 0644]
src/plugins/fetchinfo/fetchinfo_plugin.c [new file with mode: 0644]
src/plugins/fetchinfo/fetchinfo_plugin.h [new file with mode: 0644]
src/plugins/fetchinfo/fetchinfo_plugin_gtk.c [new file with mode: 0644]
src/plugins/fetchinfo/placeholder.txt [deleted file]
src/plugins/gdata/Makefile.am [new file with mode: 0644]
src/plugins/gdata/cm_gdata_contacts.c [new file with mode: 0644]
src/plugins/gdata/cm_gdata_contacts.h [new file with mode: 0644]
src/plugins/gdata/cm_gdata_prefs.c [new file with mode: 0644]
src/plugins/gdata/cm_gdata_prefs.h [new file with mode: 0644]
src/plugins/gdata/gdata_plugin.c [new file with mode: 0644]
src/plugins/gdata/gdata_plugin.h [new file with mode: 0644]
src/plugins/gdata/placeholder.txt [deleted file]
src/plugins/geolocation/placeholder.txt [deleted file]
src/plugins/gtkhtml2_viewer/placeholder.txt [deleted file]
src/plugins/mailmbox/Makefile.am [new file with mode: 0644]
src/plugins/mailmbox/carray.c [new file with mode: 0644]
src/plugins/mailmbox/carray.h [new file with mode: 0644]
src/plugins/mailmbox/chash.c [new file with mode: 0644]
src/plugins/mailmbox/chash.h [new file with mode: 0644]
src/plugins/mailmbox/clist.c [new file with mode: 0644]
src/plugins/mailmbox/clist.h [new file with mode: 0644]
src/plugins/mailmbox/mailimf.c [new file with mode: 0644]
src/plugins/mailmbox/mailimf.h [new file with mode: 0644]
src/plugins/mailmbox/mailimf_types.c [new file with mode: 0644]
src/plugins/mailmbox/mailimf_types.h [new file with mode: 0644]
src/plugins/mailmbox/mailimf_types_helper.c [new file with mode: 0644]
src/plugins/mailmbox/mailimf_types_helper.h [new file with mode: 0644]
src/plugins/mailmbox/mailimf_write.c [new file with mode: 0644]
src/plugins/mailmbox/mailimf_write.h [new file with mode: 0644]
src/plugins/mailmbox/maillock.c [new file with mode: 0644]
src/plugins/mailmbox/maillock.h [new file with mode: 0644]
src/plugins/mailmbox/mailmbox.c [new file with mode: 0644]
src/plugins/mailmbox/mailmbox.h [new file with mode: 0644]
src/plugins/mailmbox/mailmbox_folder.c [new file with mode: 0644]
src/plugins/mailmbox/mailmbox_folder.h [new file with mode: 0644]
src/plugins/mailmbox/mailmbox_gtk.deps [new file with mode: 0644]
src/plugins/mailmbox/mailmbox_parse.c [new file with mode: 0644]
src/plugins/mailmbox/mailmbox_parse.h [new file with mode: 0644]
src/plugins/mailmbox/mailmbox_types.c [new file with mode: 0644]
src/plugins/mailmbox/mailmbox_types.h [new file with mode: 0644]
src/plugins/mailmbox/mmapstring.c [new file with mode: 0644]
src/plugins/mailmbox/mmapstring.h [new file with mode: 0644]
src/plugins/mailmbox/placeholder.txt [deleted file]
src/plugins/mailmbox/plugin.c [new file with mode: 0644]
src/plugins/mailmbox/plugin_gtk.c [new file with mode: 0644]
src/plugins/mailmbox/plugin_gtk.h [new file with mode: 0644]
src/plugins/newmail/Makefile.am [new file with mode: 0644]
src/plugins/newmail/newmail.c [new file with mode: 0644]
src/plugins/newmail/placeholder.txt [deleted file]
src/plugins/notification/Makefile.am [new file with mode: 0644]
src/plugins/notification/claws.def [new file with mode: 0644]
src/plugins/notification/notification_banner.c [new file with mode: 0644]
src/plugins/notification/notification_banner.h [new file with mode: 0644]
src/plugins/notification/notification_command.c [new file with mode: 0644]
src/plugins/notification/notification_command.h [new file with mode: 0644]
src/plugins/notification/notification_core.c [new file with mode: 0644]
src/plugins/notification/notification_core.h [new file with mode: 0644]
src/plugins/notification/notification_foldercheck.c [new file with mode: 0644]
src/plugins/notification/notification_foldercheck.h [new file with mode: 0644]
src/plugins/notification/notification_hotkeys.c [new file with mode: 0644]
src/plugins/notification/notification_hotkeys.h [new file with mode: 0644]
src/plugins/notification/notification_indicator.c [new file with mode: 0644]
src/plugins/notification/notification_indicator.h [new file with mode: 0644]
src/plugins/notification/notification_lcdproc.c [new file with mode: 0644]
src/plugins/notification/notification_lcdproc.h [new file with mode: 0644]
src/plugins/notification/notification_pixbuf.c [new file with mode: 0644]
src/plugins/notification/notification_pixbuf.h [new file with mode: 0644]
src/plugins/notification/notification_plugin.c [new file with mode: 0644]
src/plugins/notification/notification_plugin.h [new file with mode: 0644]
src/plugins/notification/notification_popup.c [new file with mode: 0644]
src/plugins/notification/notification_popup.h [new file with mode: 0644]
src/plugins/notification/notification_prefs.c [new file with mode: 0644]
src/plugins/notification/notification_prefs.h [new file with mode: 0644]
src/plugins/notification/notification_trayicon.c [new file with mode: 0644]
src/plugins/notification/notification_trayicon.h [new file with mode: 0644]
src/plugins/notification/placeholder.txt [deleted file]
src/plugins/notification/plugin.def [new file with mode: 0644]
src/plugins/notification/raw_claws_mail_logo_64x64.h [new file with mode: 0644]
src/plugins/notification/version.rc [new file with mode: 0644]
src/plugins/pdf_viewer/Makefile.am [new file with mode: 0644]
src/plugins/pdf_viewer/doc_index.xpm [new file with mode: 0644]
src/plugins/pdf_viewer/doc_index_close.xpm [new file with mode: 0644]
src/plugins/pdf_viewer/doc_info.xpm [new file with mode: 0644]
src/plugins/pdf_viewer/first_arrow.xpm [new file with mode: 0644]
src/plugins/pdf_viewer/last_arrow.xpm [new file with mode: 0644]
src/plugins/pdf_viewer/left_arrow.xpm [new file with mode: 0644]
src/plugins/pdf_viewer/placeholder.txt [deleted file]
src/plugins/pdf_viewer/poppler_viewer.c [new file with mode: 0644]
src/plugins/pdf_viewer/poppler_viewer.h [new file with mode: 0644]
src/plugins/pdf_viewer/right_arrow.xpm [new file with mode: 0644]
src/plugins/pdf_viewer/rotate_left.xpm [new file with mode: 0644]
src/plugins/pdf_viewer/rotate_right.xpm [new file with mode: 0644]
src/plugins/pdf_viewer/zoom_fit.xpm [new file with mode: 0644]
src/plugins/pdf_viewer/zoom_in.xpm [new file with mode: 0644]
src/plugins/pdf_viewer/zoom_out.xpm [new file with mode: 0644]
src/plugins/pdf_viewer/zoom_width.xpm [new file with mode: 0644]
src/plugins/perl/Makefile.am [new file with mode: 0644]
src/plugins/perl/perl_gtk.c [new file with mode: 0644]
src/plugins/perl/perl_gtk.h [new file with mode: 0644]
src/plugins/perl/perl_plugin.c [new file with mode: 0644]
src/plugins/perl/perl_plugin.h [new file with mode: 0644]
src/plugins/perl/placeholder.txt [deleted file]
src/plugins/python/Makefile.am [new file with mode: 0644]
src/plugins/python/clawsmailmodule.c [new file with mode: 0644]
src/plugins/python/clawsmailmodule.h [new file with mode: 0644]
src/plugins/python/composewindowtype.c [new file with mode: 0644]
src/plugins/python/composewindowtype.h [new file with mode: 0644]
src/plugins/python/foldertype.c [new file with mode: 0644]
src/plugins/python/foldertype.h [new file with mode: 0644]
src/plugins/python/messageinfotype.c [new file with mode: 0644]
src/plugins/python/messageinfotype.h [new file with mode: 0644]
src/plugins/python/nodetype.c [new file with mode: 0644]
src/plugins/python/nodetype.h [new file with mode: 0644]
src/plugins/python/placeholder.txt [deleted file]
src/plugins/python/python-hooks.c [new file with mode: 0644]
src/plugins/python/python-hooks.h [new file with mode: 0644]
src/plugins/python/python-shell.c [new file with mode: 0644]
src/plugins/python/python-shell.h [new file with mode: 0644]
src/plugins/python/python_plugin.c [new file with mode: 0644]
src/plugins/vcalendar/Makefile.in

index 490aabfcee966698543beb6574d00d4a518b7743..52cddaf5c56630231999f3efab5f4d9a58a2e5a6 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,141 @@
+2013-02-17 [colin]     3.9.0cvs75
+
+       * src/plugins/Makefile.am
+       * src/plugins/archive/Makefile.am
+       * src/plugins/clamd/Makefile.am
+       * src/plugins/clamd/clamav_plugin.c
+       * src/plugins/clamd/clamav_plugin.h
+       * src/plugins/clamd/clamav_plugin_gtk.c
+       * src/plugins/clamd/placeholder.txt
+       * src/plugins/clamd/libclamd/Makefile.am
+       * src/plugins/clamd/libclamd/clamd-plugin.c
+       * src/plugins/clamd/libclamd/clamd-plugin.h
+       * src/plugins/fetchinfo/Makefile.am
+       * src/plugins/fetchinfo/fetchinfo_plugin.c
+       * src/plugins/fetchinfo/fetchinfo_plugin.h
+       * src/plugins/fetchinfo/fetchinfo_plugin_gtk.c
+       * src/plugins/fetchinfo/placeholder.txt
+       * src/plugins/gdata/Makefile.am
+       * src/plugins/gdata/cm_gdata_contacts.c
+       * src/plugins/gdata/cm_gdata_contacts.h
+       * src/plugins/gdata/cm_gdata_prefs.c
+       * src/plugins/gdata/cm_gdata_prefs.h
+       * src/plugins/gdata/gdata_plugin.c
+       * src/plugins/gdata/gdata_plugin.h
+       * src/plugins/gdata/placeholder.txt
+       * src/plugins/geolocation/placeholder.txt
+       * src/plugins/gtkhtml2_viewer/placeholder.txt
+       * src/plugins/mailmbox/Makefile.am
+       * src/plugins/mailmbox/carray.c
+       * src/plugins/mailmbox/carray.h
+       * src/plugins/mailmbox/chash.c
+       * src/plugins/mailmbox/chash.h
+       * src/plugins/mailmbox/clist.c
+       * src/plugins/mailmbox/clist.h
+       * src/plugins/mailmbox/mailimf.c
+       * src/plugins/mailmbox/mailimf.h
+       * src/plugins/mailmbox/mailimf_types.c
+       * src/plugins/mailmbox/mailimf_types.h
+       * src/plugins/mailmbox/mailimf_types_helper.c
+       * src/plugins/mailmbox/mailimf_types_helper.h
+       * src/plugins/mailmbox/mailimf_write.c
+       * src/plugins/mailmbox/mailimf_write.h
+       * src/plugins/mailmbox/maillock.c
+       * src/plugins/mailmbox/maillock.h
+       * src/plugins/mailmbox/mailmbox.c
+       * src/plugins/mailmbox/mailmbox.h
+       * src/plugins/mailmbox/mailmbox_folder.c
+       * src/plugins/mailmbox/mailmbox_folder.h
+       * src/plugins/mailmbox/mailmbox_gtk.deps
+       * src/plugins/mailmbox/mailmbox_parse.c
+       * src/plugins/mailmbox/mailmbox_parse.h
+       * src/plugins/mailmbox/mailmbox_types.c
+       * src/plugins/mailmbox/mailmbox_types.h
+       * src/plugins/mailmbox/mmapstring.c
+       * src/plugins/mailmbox/mmapstring.h
+       * src/plugins/mailmbox/placeholder.txt
+       * src/plugins/mailmbox/plugin.c
+       * src/plugins/mailmbox/plugin_gtk.c
+       * src/plugins/mailmbox/plugin_gtk.h
+       * src/plugins/newmail/Makefile.am
+       * src/plugins/newmail/newmail.c
+       * src/plugins/newmail/placeholder.txt
+       * src/plugins/notification/Makefile.am
+       * src/plugins/notification/claws.def
+       * src/plugins/notification/notification_banner.c
+       * src/plugins/notification/notification_banner.h
+       * src/plugins/notification/notification_command.c
+       * src/plugins/notification/notification_command.h
+       * src/plugins/notification/notification_core.c
+       * src/plugins/notification/notification_core.h
+       * src/plugins/notification/notification_foldercheck.c
+       * src/plugins/notification/notification_foldercheck.h
+       * src/plugins/notification/notification_hotkeys.c
+       * src/plugins/notification/notification_hotkeys.h
+       * src/plugins/notification/notification_indicator.c
+       * src/plugins/notification/notification_indicator.h
+       * src/plugins/notification/notification_lcdproc.c
+       * src/plugins/notification/notification_lcdproc.h
+       * src/plugins/notification/notification_pixbuf.c
+       * src/plugins/notification/notification_pixbuf.h
+       * src/plugins/notification/notification_plugin.c
+       * src/plugins/notification/notification_plugin.h
+       * src/plugins/notification/notification_popup.c
+       * src/plugins/notification/notification_popup.h
+       * src/plugins/notification/notification_prefs.c
+       * src/plugins/notification/notification_prefs.h
+       * src/plugins/notification/notification_trayicon.c
+       * src/plugins/notification/notification_trayicon.h
+       * src/plugins/notification/placeholder.txt
+       * src/plugins/notification/plugin.def
+       * src/plugins/notification/raw_claws_mail_logo_64x64.h
+       * src/plugins/notification/version.rc
+       * src/plugins/pdf_viewer/Makefile.am
+       * src/plugins/pdf_viewer/doc_index.xpm
+       * src/plugins/pdf_viewer/doc_index_close.xpm
+       * src/plugins/pdf_viewer/doc_info.xpm
+       * src/plugins/pdf_viewer/first_arrow.xpm
+       * src/plugins/pdf_viewer/last_arrow.xpm
+       * src/plugins/pdf_viewer/left_arrow.xpm
+       * src/plugins/pdf_viewer/placeholder.txt
+       * src/plugins/pdf_viewer/poppler_viewer.c
+       * src/plugins/pdf_viewer/poppler_viewer.h
+       * src/plugins/pdf_viewer/right_arrow.xpm
+       * src/plugins/pdf_viewer/rotate_left.xpm
+       * src/plugins/pdf_viewer/rotate_right.xpm
+       * src/plugins/pdf_viewer/zoom_fit.xpm
+       * src/plugins/pdf_viewer/zoom_in.xpm
+       * src/plugins/pdf_viewer/zoom_out.xpm
+       * src/plugins/pdf_viewer/zoom_width.xpm
+       * src/plugins/perl/Makefile.am
+       * src/plugins/perl/perl_gtk.c
+       * src/plugins/perl/perl_gtk.h
+       * src/plugins/perl/perl_plugin.c
+       * src/plugins/perl/perl_plugin.h
+       * src/plugins/perl/placeholder.txt
+       * src/plugins/python/Makefile.am
+       * src/plugins/python/clawsmailmodule.c
+       * src/plugins/python/clawsmailmodule.h
+       * src/plugins/python/composewindowtype.c
+       * src/plugins/python/composewindowtype.h
+       * src/plugins/python/foldertype.c
+       * src/plugins/python/foldertype.h
+       * src/plugins/python/messageinfotype.c
+       * src/plugins/python/messageinfotype.h
+       * src/plugins/python/nodetype.c
+       * src/plugins/python/nodetype.h
+       * src/plugins/python/placeholder.txt
+       * src/plugins/python/python-hooks.c
+       * src/plugins/python/python-hooks.h
+       * src/plugins/python/python-shell.c
+       * src/plugins/python/python-shell.h
+       * src/plugins/python/python_plugin.c
+       * src/plugins/vcalendar/Makefile.in
+               Add some plugins (clamd, fetchinfo, gdata, mailmbox, newmail,
+               notification, pdf_viewer, perl, python). Notification not yet
+               enabled because it has too much autoconf switches for my taste.
+
+
 2013-02-16 [colin]     3.9.0cvs74
 
        * po/POTFILES.in
 2013-02-16 [colin]     3.9.0cvs74
 
        * po/POTFILES.in
index 0411f24c121673c59b43af4eccdd84206e464703..cedcf98407a688f799b3a87bac8a2580077b0420 100644 (file)
--- a/PATCHSETS
+++ b/PATCHSETS
 ( cvs diff -u -r 1.53.2.41 -r 1.53.2.42 po/POTFILES.in;  cvs diff -u -r 1.21.2.11 -r 1.21.2.12 po/bg.po;  cvs diff -u -r 1.1.2.21 -r 1.1.2.22 po/ca.po;  cvs diff -u -r 1.9.2.28 -r 1.9.2.29 po/cs.po;  cvs diff -u -r 1.58.2.54 -r 1.58.2.55 po/de.po;  cvs diff -u -r 1.12.2.20 -r 1.12.2.21 po/en_GB.po;  cvs diff -u -r 1.60.2.68 -r 1.60.2.69 po/es.po;  cvs diff -u -r 1.1.2.31 -r 1.1.2.32 po/fi.po;  cvs diff -u -r 1.42.2.61 -r 1.42.2.62 po/fr.po;  cvs diff -u -r 1.5.2.25 -r 1.5.2.26 po/hu.po;  cvs diff -u -r 1.1.2.5 -r 1.1.2.6 po/id_ID.po;  cvs diff -u -r 1.34.2.32 -r 1.34.2.33 po/it.po;  cvs diff -u -r 1.16.2.14 -r 1.16.2.15 po/ja.po;  cvs diff -u -r 1.1.2.7 -r 1.1.2.8 po/lt.po;  cvs diff -u -r 1.28.2.17 -r 1.28.2.18 po/nl.po;  cvs diff -u -r 1.10.2.20 -r 1.10.2.21 po/pl.po;  cvs diff -u -r 1.50.2.47 -r 1.50.2.48 po/pt_BR.po;  cvs diff -u -r 1.1.2.6 -r 1.1.2.7 po/pt_PT.po;  cvs diff -u -r 1.17.2.24 -r 1.17.2.25 po/ru.po;  cvs diff -u -r 1.2.2.39 -r 1.2.2.40 po/sk.po;  cvs diff -u -r 1.17.2.30 -r 1.17.2.31 po/sr.po;  cvs diff -u -r 1.4.2.15 -r 1.4.2.16 po/sv.po;  cvs diff -u -r 1.1.2.8 -r 1.1.2.9 po/uk.po;  cvs diff -u -r 1.5.2.33 -r 1.5.2.34 po/zh_CN.po;  cvs diff -u -r 1.1.2.7 -r 1.1.2.8 po/zh_TW.po;  ) > 3.9.0cvs72.patchset
 ( cvs diff -u -r 1.8.2.11 -r 1.8.2.12 src/plugins/Makefile.am;  diff -u /dev/null src/plugins/acpi_notifier/Makefile.am;  diff -u /dev/null src/plugins/acpi_notifier/acpi_notifier.c;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/acpi_notifier/placeholder.txt;  diff -u /dev/null src/plugins/address_keeper/Makefile.am;  diff -u /dev/null src/plugins/address_keeper/address_keeper.c;  diff -u /dev/null src/plugins/address_keeper/address_keeper.h;  diff -u /dev/null src/plugins/address_keeper/address_keeper_prefs.c;  diff -u /dev/null src/plugins/address_keeper/address_keeper_prefs.h;  diff -u /dev/null src/plugins/address_keeper/claws.def;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/address_keeper/placeholder.txt;  diff -u /dev/null src/plugins/address_keeper/plugin.def;  diff -u /dev/null src/plugins/address_keeper/version.rc;  diff -u /dev/null src/plugins/attachwarner/Makefile.am;  diff -u /dev/null src/plugins/attachwarner/attachwarner.c;  diff -u /dev/null src/plugins/attachwarner/attachwarner.h;  diff -u /dev/null src/plugins/attachwarner/attachwarner_prefs.c;  diff -u /dev/null src/plugins/attachwarner/attachwarner_prefs.h;  diff -u /dev/null src/plugins/attachwarner/claws.def;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/attachwarner/placeholder.txt;  diff -u /dev/null src/plugins/attachwarner/plugin.def;  diff -u /dev/null src/plugins/attachwarner/version.rc;  cvs diff -u -r 1.1.2.2 -r 1.1.2.3 src/plugins/vcalendar/libical/libical/icalversion.h;  cvs diff -u -r 1.53.2.42 -r 1.53.2.43 po/POTFILES.in;  cvs diff -u -r 1.21.2.12 -r 1.21.2.13 po/bg.po;  cvs diff -u -r 1.1.2.22 -r 1.1.2.23 po/ca.po;  cvs diff -u -r 1.9.2.29 -r 1.9.2.30 po/cs.po;  cvs diff -u -r 1.58.2.55 -r 1.58.2.56 po/de.po;  cvs diff -u -r 1.12.2.21 -r 1.12.2.22 po/en_GB.po;  cvs diff -u -r 1.60.2.69 -r 1.60.2.70 po/es.po;  cvs diff -u -r 1.1.2.32 -r 1.1.2.33 po/fi.po;  cvs diff -u -r 1.42.2.62 -r 1.42.2.63 po/fr.po;  cvs diff -u -r 1.5.2.26 -r 1.5.2.27 po/hu.po;  cvs diff -u -r 1.1.2.6 -r 1.1.2.7 po/id_ID.po;  cvs diff -u -r 1.34.2.33 -r 1.34.2.34 po/it.po;  cvs diff -u -r 1.16.2.15 -r 1.16.2.16 po/ja.po;  cvs diff -u -r 1.1.2.8 -r 1.1.2.9 po/lt.po;  cvs diff -u -r 1.28.2.18 -r 1.28.2.19 po/nl.po;  cvs diff -u -r 1.10.2.21 -r 1.10.2.22 po/pl.po;  cvs diff -u -r 1.50.2.48 -r 1.50.2.49 po/pt_BR.po;  cvs diff -u -r 1.1.2.7 -r 1.1.2.8 po/pt_PT.po;  cvs diff -u -r 1.17.2.25 -r 1.17.2.26 po/ru.po;  cvs diff -u -r 1.2.2.40 -r 1.2.2.41 po/sk.po;  cvs diff -u -r 1.17.2.31 -r 1.17.2.32 po/sr.po;  cvs diff -u -r 1.4.2.16 -r 1.4.2.17 po/sv.po;  cvs diff -u -r 1.1.2.9 -r 1.1.2.10 po/uk.po;  cvs diff -u -r 1.5.2.34 -r 1.5.2.35 po/zh_CN.po;  cvs diff -u -r 1.1.2.8 -r 1.1.2.9 po/zh_TW.po;  ) > 3.9.0cvs73.patchset
 ( cvs diff -u -r 1.53.2.43 -r 1.53.2.44 po/POTFILES.in;  cvs diff -u -r 1.21.2.13 -r 1.21.2.14 po/bg.po;  cvs diff -u -r 1.1.2.23 -r 1.1.2.24 po/ca.po;  cvs diff -u -r 1.9.2.30 -r 1.9.2.31 po/cs.po;  cvs diff -u -r 1.58.2.56 -r 1.58.2.57 po/de.po;  cvs diff -u -r 1.12.2.22 -r 1.12.2.23 po/en_GB.po;  cvs diff -u -r 1.60.2.70 -r 1.60.2.71 po/es.po;  cvs diff -u -r 1.1.2.33 -r 1.1.2.34 po/fi.po;  cvs diff -u -r 1.42.2.63 -r 1.42.2.64 po/fr.po;  cvs diff -u -r 1.5.2.27 -r 1.5.2.28 po/hu.po;  cvs diff -u -r 1.1.2.7 -r 1.1.2.8 po/id_ID.po;  cvs diff -u -r 1.34.2.34 -r 1.34.2.35 po/it.po;  cvs diff -u -r 1.16.2.16 -r 1.16.2.17 po/ja.po;  cvs diff -u -r 1.1.2.9 -r 1.1.2.10 po/lt.po;  cvs diff -u -r 1.28.2.19 -r 1.28.2.20 po/nl.po;  cvs diff -u -r 1.10.2.22 -r 1.10.2.23 po/pl.po;  cvs diff -u -r 1.50.2.49 -r 1.50.2.50 po/pt_BR.po;  cvs diff -u -r 1.1.2.8 -r 1.1.2.9 po/pt_PT.po;  cvs diff -u -r 1.17.2.26 -r 1.17.2.27 po/ru.po;  cvs diff -u -r 1.2.2.41 -r 1.2.2.42 po/sk.po;  cvs diff -u -r 1.4.2.17 -r 1.4.2.18 po/sv.po;  cvs diff -u -r 1.1.2.10 -r 1.1.2.11 po/uk.po;  cvs diff -u -r 1.5.2.35 -r 1.5.2.36 po/zh_CN.po;  cvs diff -u -r 1.1.2.9 -r 1.1.2.10 po/zh_TW.po;  cvs diff -u -r 1.8.2.12 -r 1.8.2.13 src/plugins/Makefile.am;  diff -u /dev/null src/plugins/archive/.cvsignore;  diff -u /dev/null src/plugins/archive/Makefile.am;  diff -u /dev/null src/plugins/archive/archiver.c;  diff -u /dev/null src/plugins/archive/archiver.h;  diff -u /dev/null src/plugins/archive/archiver_gtk.c;  diff -u /dev/null src/plugins/archive/archiver_prefs.c;  diff -u /dev/null src/plugins/archive/archiver_prefs.h;  diff -u /dev/null src/plugins/archive/libarchive_archive.c;  diff -u /dev/null src/plugins/archive/libarchive_archive.h;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/archive/placeholder.txt;  diff -u /dev/null src/plugins/att_remover/.cvsignore;  diff -u /dev/null src/plugins/att_remover/Makefile.am;  diff -u /dev/null src/plugins/att_remover/att_remover.c;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/att_remover/placeholder.txt;  diff -u /dev/null src/plugins/bsfilter/.cvsignore;  diff -u /dev/null src/plugins/bsfilter/Makefile.am;  diff -u /dev/null src/plugins/bsfilter/bsfilter.c;  diff -u /dev/null src/plugins/bsfilter/bsfilter.h;  diff -u /dev/null src/plugins/bsfilter/bsfilter_gtk.c;  diff -u /dev/null src/plugins/bsfilter/claws.def;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/bsfilter/placeholder.txt;  diff -u /dev/null src/plugins/bsfilter/plugin.def;  diff -u /dev/null src/plugins/bsfilter/version.rc;  cvs diff -u -r 1.1.2.2 -r 1.1.2.3 src/plugins/vcalendar/Makefile.in;  cvs diff -u -r -1.1.2.3 -r -1.1.2.4 src/plugins/vcalendar/libical/libical/icalversion.h;  ) > 3.9.0cvs74.patchset
 ( cvs diff -u -r 1.53.2.41 -r 1.53.2.42 po/POTFILES.in;  cvs diff -u -r 1.21.2.11 -r 1.21.2.12 po/bg.po;  cvs diff -u -r 1.1.2.21 -r 1.1.2.22 po/ca.po;  cvs diff -u -r 1.9.2.28 -r 1.9.2.29 po/cs.po;  cvs diff -u -r 1.58.2.54 -r 1.58.2.55 po/de.po;  cvs diff -u -r 1.12.2.20 -r 1.12.2.21 po/en_GB.po;  cvs diff -u -r 1.60.2.68 -r 1.60.2.69 po/es.po;  cvs diff -u -r 1.1.2.31 -r 1.1.2.32 po/fi.po;  cvs diff -u -r 1.42.2.61 -r 1.42.2.62 po/fr.po;  cvs diff -u -r 1.5.2.25 -r 1.5.2.26 po/hu.po;  cvs diff -u -r 1.1.2.5 -r 1.1.2.6 po/id_ID.po;  cvs diff -u -r 1.34.2.32 -r 1.34.2.33 po/it.po;  cvs diff -u -r 1.16.2.14 -r 1.16.2.15 po/ja.po;  cvs diff -u -r 1.1.2.7 -r 1.1.2.8 po/lt.po;  cvs diff -u -r 1.28.2.17 -r 1.28.2.18 po/nl.po;  cvs diff -u -r 1.10.2.20 -r 1.10.2.21 po/pl.po;  cvs diff -u -r 1.50.2.47 -r 1.50.2.48 po/pt_BR.po;  cvs diff -u -r 1.1.2.6 -r 1.1.2.7 po/pt_PT.po;  cvs diff -u -r 1.17.2.24 -r 1.17.2.25 po/ru.po;  cvs diff -u -r 1.2.2.39 -r 1.2.2.40 po/sk.po;  cvs diff -u -r 1.17.2.30 -r 1.17.2.31 po/sr.po;  cvs diff -u -r 1.4.2.15 -r 1.4.2.16 po/sv.po;  cvs diff -u -r 1.1.2.8 -r 1.1.2.9 po/uk.po;  cvs diff -u -r 1.5.2.33 -r 1.5.2.34 po/zh_CN.po;  cvs diff -u -r 1.1.2.7 -r 1.1.2.8 po/zh_TW.po;  ) > 3.9.0cvs72.patchset
 ( cvs diff -u -r 1.8.2.11 -r 1.8.2.12 src/plugins/Makefile.am;  diff -u /dev/null src/plugins/acpi_notifier/Makefile.am;  diff -u /dev/null src/plugins/acpi_notifier/acpi_notifier.c;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/acpi_notifier/placeholder.txt;  diff -u /dev/null src/plugins/address_keeper/Makefile.am;  diff -u /dev/null src/plugins/address_keeper/address_keeper.c;  diff -u /dev/null src/plugins/address_keeper/address_keeper.h;  diff -u /dev/null src/plugins/address_keeper/address_keeper_prefs.c;  diff -u /dev/null src/plugins/address_keeper/address_keeper_prefs.h;  diff -u /dev/null src/plugins/address_keeper/claws.def;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/address_keeper/placeholder.txt;  diff -u /dev/null src/plugins/address_keeper/plugin.def;  diff -u /dev/null src/plugins/address_keeper/version.rc;  diff -u /dev/null src/plugins/attachwarner/Makefile.am;  diff -u /dev/null src/plugins/attachwarner/attachwarner.c;  diff -u /dev/null src/plugins/attachwarner/attachwarner.h;  diff -u /dev/null src/plugins/attachwarner/attachwarner_prefs.c;  diff -u /dev/null src/plugins/attachwarner/attachwarner_prefs.h;  diff -u /dev/null src/plugins/attachwarner/claws.def;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/attachwarner/placeholder.txt;  diff -u /dev/null src/plugins/attachwarner/plugin.def;  diff -u /dev/null src/plugins/attachwarner/version.rc;  cvs diff -u -r 1.1.2.2 -r 1.1.2.3 src/plugins/vcalendar/libical/libical/icalversion.h;  cvs diff -u -r 1.53.2.42 -r 1.53.2.43 po/POTFILES.in;  cvs diff -u -r 1.21.2.12 -r 1.21.2.13 po/bg.po;  cvs diff -u -r 1.1.2.22 -r 1.1.2.23 po/ca.po;  cvs diff -u -r 1.9.2.29 -r 1.9.2.30 po/cs.po;  cvs diff -u -r 1.58.2.55 -r 1.58.2.56 po/de.po;  cvs diff -u -r 1.12.2.21 -r 1.12.2.22 po/en_GB.po;  cvs diff -u -r 1.60.2.69 -r 1.60.2.70 po/es.po;  cvs diff -u -r 1.1.2.32 -r 1.1.2.33 po/fi.po;  cvs diff -u -r 1.42.2.62 -r 1.42.2.63 po/fr.po;  cvs diff -u -r 1.5.2.26 -r 1.5.2.27 po/hu.po;  cvs diff -u -r 1.1.2.6 -r 1.1.2.7 po/id_ID.po;  cvs diff -u -r 1.34.2.33 -r 1.34.2.34 po/it.po;  cvs diff -u -r 1.16.2.15 -r 1.16.2.16 po/ja.po;  cvs diff -u -r 1.1.2.8 -r 1.1.2.9 po/lt.po;  cvs diff -u -r 1.28.2.18 -r 1.28.2.19 po/nl.po;  cvs diff -u -r 1.10.2.21 -r 1.10.2.22 po/pl.po;  cvs diff -u -r 1.50.2.48 -r 1.50.2.49 po/pt_BR.po;  cvs diff -u -r 1.1.2.7 -r 1.1.2.8 po/pt_PT.po;  cvs diff -u -r 1.17.2.25 -r 1.17.2.26 po/ru.po;  cvs diff -u -r 1.2.2.40 -r 1.2.2.41 po/sk.po;  cvs diff -u -r 1.17.2.31 -r 1.17.2.32 po/sr.po;  cvs diff -u -r 1.4.2.16 -r 1.4.2.17 po/sv.po;  cvs diff -u -r 1.1.2.9 -r 1.1.2.10 po/uk.po;  cvs diff -u -r 1.5.2.34 -r 1.5.2.35 po/zh_CN.po;  cvs diff -u -r 1.1.2.8 -r 1.1.2.9 po/zh_TW.po;  ) > 3.9.0cvs73.patchset
 ( cvs diff -u -r 1.53.2.43 -r 1.53.2.44 po/POTFILES.in;  cvs diff -u -r 1.21.2.13 -r 1.21.2.14 po/bg.po;  cvs diff -u -r 1.1.2.23 -r 1.1.2.24 po/ca.po;  cvs diff -u -r 1.9.2.30 -r 1.9.2.31 po/cs.po;  cvs diff -u -r 1.58.2.56 -r 1.58.2.57 po/de.po;  cvs diff -u -r 1.12.2.22 -r 1.12.2.23 po/en_GB.po;  cvs diff -u -r 1.60.2.70 -r 1.60.2.71 po/es.po;  cvs diff -u -r 1.1.2.33 -r 1.1.2.34 po/fi.po;  cvs diff -u -r 1.42.2.63 -r 1.42.2.64 po/fr.po;  cvs diff -u -r 1.5.2.27 -r 1.5.2.28 po/hu.po;  cvs diff -u -r 1.1.2.7 -r 1.1.2.8 po/id_ID.po;  cvs diff -u -r 1.34.2.34 -r 1.34.2.35 po/it.po;  cvs diff -u -r 1.16.2.16 -r 1.16.2.17 po/ja.po;  cvs diff -u -r 1.1.2.9 -r 1.1.2.10 po/lt.po;  cvs diff -u -r 1.28.2.19 -r 1.28.2.20 po/nl.po;  cvs diff -u -r 1.10.2.22 -r 1.10.2.23 po/pl.po;  cvs diff -u -r 1.50.2.49 -r 1.50.2.50 po/pt_BR.po;  cvs diff -u -r 1.1.2.8 -r 1.1.2.9 po/pt_PT.po;  cvs diff -u -r 1.17.2.26 -r 1.17.2.27 po/ru.po;  cvs diff -u -r 1.2.2.41 -r 1.2.2.42 po/sk.po;  cvs diff -u -r 1.4.2.17 -r 1.4.2.18 po/sv.po;  cvs diff -u -r 1.1.2.10 -r 1.1.2.11 po/uk.po;  cvs diff -u -r 1.5.2.35 -r 1.5.2.36 po/zh_CN.po;  cvs diff -u -r 1.1.2.9 -r 1.1.2.10 po/zh_TW.po;  cvs diff -u -r 1.8.2.12 -r 1.8.2.13 src/plugins/Makefile.am;  diff -u /dev/null src/plugins/archive/.cvsignore;  diff -u /dev/null src/plugins/archive/Makefile.am;  diff -u /dev/null src/plugins/archive/archiver.c;  diff -u /dev/null src/plugins/archive/archiver.h;  diff -u /dev/null src/plugins/archive/archiver_gtk.c;  diff -u /dev/null src/plugins/archive/archiver_prefs.c;  diff -u /dev/null src/plugins/archive/archiver_prefs.h;  diff -u /dev/null src/plugins/archive/libarchive_archive.c;  diff -u /dev/null src/plugins/archive/libarchive_archive.h;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/archive/placeholder.txt;  diff -u /dev/null src/plugins/att_remover/.cvsignore;  diff -u /dev/null src/plugins/att_remover/Makefile.am;  diff -u /dev/null src/plugins/att_remover/att_remover.c;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/att_remover/placeholder.txt;  diff -u /dev/null src/plugins/bsfilter/.cvsignore;  diff -u /dev/null src/plugins/bsfilter/Makefile.am;  diff -u /dev/null src/plugins/bsfilter/bsfilter.c;  diff -u /dev/null src/plugins/bsfilter/bsfilter.h;  diff -u /dev/null src/plugins/bsfilter/bsfilter_gtk.c;  diff -u /dev/null src/plugins/bsfilter/claws.def;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/bsfilter/placeholder.txt;  diff -u /dev/null src/plugins/bsfilter/plugin.def;  diff -u /dev/null src/plugins/bsfilter/version.rc;  cvs diff -u -r 1.1.2.2 -r 1.1.2.3 src/plugins/vcalendar/Makefile.in;  cvs diff -u -r -1.1.2.3 -r -1.1.2.4 src/plugins/vcalendar/libical/libical/icalversion.h;  ) > 3.9.0cvs74.patchset
+( cvs diff -u -r 1.8.2.13 -r 1.8.2.14 src/plugins/Makefile.am;  cvs diff -u -r 1.1.2.1 -r 1.1.2.2 src/plugins/archive/Makefile.am;  diff -u /dev/null src/plugins/clamd/Makefile.am;  diff -u /dev/null src/plugins/clamd/clamav_plugin.c;  diff -u /dev/null src/plugins/clamd/clamav_plugin.h;  diff -u /dev/null src/plugins/clamd/clamav_plugin_gtk.c;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/clamd/placeholder.txt;  diff -u /dev/null src/plugins/clamd/libclamd/Makefile.am;  diff -u /dev/null src/plugins/clamd/libclamd/clamd-plugin.c;  diff -u /dev/null src/plugins/clamd/libclamd/clamd-plugin.h;  diff -u /dev/null src/plugins/fetchinfo/Makefile.am;  diff -u /dev/null src/plugins/fetchinfo/fetchinfo_plugin.c;  diff -u /dev/null src/plugins/fetchinfo/fetchinfo_plugin.h;  diff -u /dev/null src/plugins/fetchinfo/fetchinfo_plugin_gtk.c;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/fetchinfo/placeholder.txt;  diff -u /dev/null src/plugins/gdata/Makefile.am;  diff -u /dev/null src/plugins/gdata/cm_gdata_contacts.c;  diff -u /dev/null src/plugins/gdata/cm_gdata_contacts.h;  diff -u /dev/null src/plugins/gdata/cm_gdata_prefs.c;  diff -u /dev/null src/plugins/gdata/cm_gdata_prefs.h;  diff -u /dev/null src/plugins/gdata/gdata_plugin.c;  diff -u /dev/null src/plugins/gdata/gdata_plugin.h;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/gdata/placeholder.txt;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/geolocation/placeholder.txt;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/gtkhtml2_viewer/placeholder.txt;  diff -u /dev/null src/plugins/mailmbox/Makefile.am;  diff -u /dev/null src/plugins/mailmbox/carray.c;  diff -u /dev/null src/plugins/mailmbox/carray.h;  diff -u /dev/null src/plugins/mailmbox/chash.c;  diff -u /dev/null src/plugins/mailmbox/chash.h;  diff -u /dev/null src/plugins/mailmbox/clist.c;  diff -u /dev/null src/plugins/mailmbox/clist.h;  diff -u /dev/null src/plugins/mailmbox/mailimf.c;  diff -u /dev/null src/plugins/mailmbox/mailimf.h;  diff -u /dev/null src/plugins/mailmbox/mailimf_types.c;  diff -u /dev/null src/plugins/mailmbox/mailimf_types.h;  diff -u /dev/null src/plugins/mailmbox/mailimf_types_helper.c;  diff -u /dev/null src/plugins/mailmbox/mailimf_types_helper.h;  diff -u /dev/null src/plugins/mailmbox/mailimf_write.c;  diff -u /dev/null src/plugins/mailmbox/mailimf_write.h;  diff -u /dev/null src/plugins/mailmbox/maillock.c;  diff -u /dev/null src/plugins/mailmbox/maillock.h;  diff -u /dev/null src/plugins/mailmbox/mailmbox.c;  diff -u /dev/null src/plugins/mailmbox/mailmbox.h;  diff -u /dev/null src/plugins/mailmbox/mailmbox_folder.c;  diff -u /dev/null src/plugins/mailmbox/mailmbox_folder.h;  diff -u /dev/null src/plugins/mailmbox/mailmbox_gtk.deps;  diff -u /dev/null src/plugins/mailmbox/mailmbox_parse.c;  diff -u /dev/null src/plugins/mailmbox/mailmbox_parse.h;  diff -u /dev/null src/plugins/mailmbox/mailmbox_types.c;  diff -u /dev/null src/plugins/mailmbox/mailmbox_types.h;  diff -u /dev/null src/plugins/mailmbox/mmapstring.c;  diff -u /dev/null src/plugins/mailmbox/mmapstring.h;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/mailmbox/placeholder.txt;  diff -u /dev/null src/plugins/mailmbox/plugin.c;  diff -u /dev/null src/plugins/mailmbox/plugin_gtk.c;  diff -u /dev/null src/plugins/mailmbox/plugin_gtk.h;  diff -u /dev/null src/plugins/newmail/Makefile.am;  diff -u /dev/null src/plugins/newmail/newmail.c;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/newmail/placeholder.txt;  diff -u /dev/null src/plugins/notification/Makefile.am;  diff -u /dev/null src/plugins/notification/claws.def;  diff -u /dev/null src/plugins/notification/notification_banner.c;  diff -u /dev/null src/plugins/notification/notification_banner.h;  diff -u /dev/null src/plugins/notification/notification_command.c;  diff -u /dev/null src/plugins/notification/notification_command.h;  diff -u /dev/null src/plugins/notification/notification_core.c;  diff -u /dev/null src/plugins/notification/notification_core.h;  diff -u /dev/null src/plugins/notification/notification_foldercheck.c;  diff -u /dev/null src/plugins/notification/notification_foldercheck.h;  diff -u /dev/null src/plugins/notification/notification_hotkeys.c;  diff -u /dev/null src/plugins/notification/notification_hotkeys.h;  diff -u /dev/null src/plugins/notification/notification_indicator.c;  diff -u /dev/null src/plugins/notification/notification_indicator.h;  diff -u /dev/null src/plugins/notification/notification_lcdproc.c;  diff -u /dev/null src/plugins/notification/notification_lcdproc.h;  diff -u /dev/null src/plugins/notification/notification_pixbuf.c;  diff -u /dev/null src/plugins/notification/notification_pixbuf.h;  diff -u /dev/null src/plugins/notification/notification_plugin.c;  diff -u /dev/null src/plugins/notification/notification_plugin.h;  diff -u /dev/null src/plugins/notification/notification_popup.c;  diff -u /dev/null src/plugins/notification/notification_popup.h;  diff -u /dev/null src/plugins/notification/notification_prefs.c;  diff -u /dev/null src/plugins/notification/notification_prefs.h;  diff -u /dev/null src/plugins/notification/notification_trayicon.c;  diff -u /dev/null src/plugins/notification/notification_trayicon.h;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/notification/placeholder.txt;  diff -u /dev/null src/plugins/notification/plugin.def;  diff -u /dev/null src/plugins/notification/raw_claws_mail_logo_64x64.h;  diff -u /dev/null src/plugins/notification/version.rc;  diff -u /dev/null src/plugins/pdf_viewer/Makefile.am;  diff -u /dev/null src/plugins/pdf_viewer/doc_index.xpm;  diff -u /dev/null src/plugins/pdf_viewer/doc_index_close.xpm;  diff -u /dev/null src/plugins/pdf_viewer/doc_info.xpm;  diff -u /dev/null src/plugins/pdf_viewer/first_arrow.xpm;  diff -u /dev/null src/plugins/pdf_viewer/last_arrow.xpm;  diff -u /dev/null src/plugins/pdf_viewer/left_arrow.xpm;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/pdf_viewer/placeholder.txt;  diff -u /dev/null src/plugins/pdf_viewer/poppler_viewer.c;  diff -u /dev/null src/plugins/pdf_viewer/poppler_viewer.h;  diff -u /dev/null src/plugins/pdf_viewer/right_arrow.xpm;  diff -u /dev/null src/plugins/pdf_viewer/rotate_left.xpm;  diff -u /dev/null src/plugins/pdf_viewer/rotate_right.xpm;  diff -u /dev/null src/plugins/pdf_viewer/zoom_fit.xpm;  diff -u /dev/null src/plugins/pdf_viewer/zoom_in.xpm;  diff -u /dev/null src/plugins/pdf_viewer/zoom_out.xpm;  diff -u /dev/null src/plugins/pdf_viewer/zoom_width.xpm;  diff -u /dev/null src/plugins/perl/Makefile.am;  diff -u /dev/null src/plugins/perl/perl_gtk.c;  diff -u /dev/null src/plugins/perl/perl_gtk.h;  diff -u /dev/null src/plugins/perl/perl_plugin.c;  diff -u /dev/null src/plugins/perl/perl_plugin.h;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/perl/placeholder.txt;  diff -u /dev/null src/plugins/python/Makefile.am;  diff -u /dev/null src/plugins/python/clawsmailmodule.c;  diff -u /dev/null src/plugins/python/clawsmailmodule.h;  diff -u /dev/null src/plugins/python/composewindowtype.c;  diff -u /dev/null src/plugins/python/composewindowtype.h;  diff -u /dev/null src/plugins/python/foldertype.c;  diff -u /dev/null src/plugins/python/foldertype.h;  diff -u /dev/null src/plugins/python/messageinfotype.c;  diff -u /dev/null src/plugins/python/messageinfotype.h;  diff -u /dev/null src/plugins/python/nodetype.c;  diff -u /dev/null src/plugins/python/nodetype.h;  cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/python/placeholder.txt;  diff -u /dev/null src/plugins/python/python-hooks.c;  diff -u /dev/null src/plugins/python/python-hooks.h;  diff -u /dev/null src/plugins/python/python-shell.c;  diff -u /dev/null src/plugins/python/python-shell.h;  diff -u /dev/null src/plugins/python/python_plugin.c;  cvs diff -u -r 1.1.2.3 -r 1.1.2.4 src/plugins/vcalendar/Makefile.in;  ) > 3.9.0cvs75.patchset
index 26c6db85eeb76c175a75156816f196846d94d2f4..cff9df336960859a5881dde8f56ba3796985c19c 100644 (file)
@@ -12,7 +12,7 @@ MINOR_VERSION=9
 MICRO_VERSION=0
 INTERFACE_AGE=0
 BINARY_AGE=0
 MICRO_VERSION=0
 INTERFACE_AGE=0
 BINARY_AGE=0
-EXTRA_VERSION=74
+EXTRA_VERSION=75
 EXTRA_RELEASE=
 EXTRA_GTK2_VERSION=
 
 EXTRA_RELEASE=
 EXTRA_GTK2_VERSION=
 
@@ -863,6 +863,9 @@ AC_ARG_ENABLE(acpi_notifier-plugin,
        [ac_cv_enable_acpi_notifier_plugin=$enableval], [ac_cv_enable_acpi_notifier_plugin=yes])
 if test x"$ac_cv_enable_acpi_notifier_plugin" = xyes; then
        PLUGINS="acpi_notifier $PLUGINS"
        [ac_cv_enable_acpi_notifier_plugin=$enableval], [ac_cv_enable_acpi_notifier_plugin=yes])
 if test x"$ac_cv_enable_acpi_notifier_plugin" = xyes; then
        PLUGINS="acpi_notifier $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
 fi
 AM_CONDITIONAL(BUILD_ACPI_NOTIFIER_PLUGIN, test x"$ac_cv_enable_acpi_notifier_plugin" = xyes)
        
 fi
 AM_CONDITIONAL(BUILD_ACPI_NOTIFIER_PLUGIN, test x"$ac_cv_enable_acpi_notifier_plugin" = xyes)
        
@@ -872,6 +875,9 @@ AC_ARG_ENABLE(address_keeper-plugin,
        [ac_cv_enable_address_keeper_plugin=$enableval], [ac_cv_enable_address_keeper_plugin=yes])
 if test x"$ac_cv_enable_address_keeper_plugin" = xyes; then
        PLUGINS="address_keeper $PLUGINS"
        [ac_cv_enable_address_keeper_plugin=$enableval], [ac_cv_enable_address_keeper_plugin=yes])
 if test x"$ac_cv_enable_address_keeper_plugin" = xyes; then
        PLUGINS="address_keeper $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
 fi
 AM_CONDITIONAL(BUILD_ADDRESS_KEEPER_PLUGIN, test x"$ac_cv_enable_address_keeper_plugin" = xyes)
        
 fi
 AM_CONDITIONAL(BUILD_ADDRESS_KEEPER_PLUGIN, test x"$ac_cv_enable_address_keeper_plugin" = xyes)
        
@@ -887,6 +893,9 @@ if test x"$ac_cv_enable_archive_plugin" = xyes; then
                       exit 1
                       )
        PLUGINS="archive $PLUGINS"
                       exit 1
                       )
        PLUGINS="archive $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
 fi
 AM_CONDITIONAL(BUILD_ARCHIVE_PLUGIN, test x"$ac_cv_enable_archive_plugin" = xyes)
 
 fi
 AM_CONDITIONAL(BUILD_ARCHIVE_PLUGIN, test x"$ac_cv_enable_archive_plugin" = xyes)
 
@@ -896,6 +905,9 @@ AC_ARG_ENABLE(att_remover-plugin,
        [ac_cv_enable_att_remover_plugin=$enableval], [ac_cv_enable_att_remover_plugin=yes])
 if test x"$ac_cv_enable_att_remover_plugin" = xyes; then
        PLUGINS="att_remover $PLUGINS"
        [ac_cv_enable_att_remover_plugin=$enableval], [ac_cv_enable_att_remover_plugin=yes])
 if test x"$ac_cv_enable_att_remover_plugin" = xyes; then
        PLUGINS="att_remover $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
 fi
 AM_CONDITIONAL(BUILD_ATT_REMOVER_PLUGIN, test x"$ac_cv_enable_att_remover_plugin" = xyes)
 
 fi
 AM_CONDITIONAL(BUILD_ATT_REMOVER_PLUGIN, test x"$ac_cv_enable_att_remover_plugin" = xyes)
 
@@ -905,6 +917,9 @@ AC_ARG_ENABLE(attachwarner-plugin,
        [ac_cv_enable_attachwarner_plugin=$enableval], [ac_cv_enable_attachwarner_plugin=yes])
 if test x"$ac_cv_enable_attachwarner_plugin" = xyes; then
        PLUGINS="attachwarner $PLUGINS"
        [ac_cv_enable_attachwarner_plugin=$enableval], [ac_cv_enable_attachwarner_plugin=yes])
 if test x"$ac_cv_enable_attachwarner_plugin" = xyes; then
        PLUGINS="attachwarner $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
 fi
 AM_CONDITIONAL(BUILD_ATTACHWARNER_PLUGIN, test x"$ac_cv_enable_attachwarner_plugin" = xyes)
 
 fi
 AM_CONDITIONAL(BUILD_ATTACHWARNER_PLUGIN, test x"$ac_cv_enable_attachwarner_plugin" = xyes)
 
@@ -929,15 +944,34 @@ AC_ARG_ENABLE(bsfilter-plugin,
        [ac_cv_enable_bsfilter_plugin=$enableval], [ac_cv_enable_bsfilter_plugin=yes])
 if test x"$ac_cv_enable_bsfilter_plugin" = xyes; then
        PLUGINS="bsfilter $PLUGINS"
        [ac_cv_enable_bsfilter_plugin=$enableval], [ac_cv_enable_bsfilter_plugin=yes])
 if test x"$ac_cv_enable_bsfilter_plugin" = xyes; then
        PLUGINS="bsfilter $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
 fi
 AM_CONDITIONAL(BUILD_BSFILTER_PLUGIN, test x"$ac_cv_enable_bsfilter_plugin" = xyes)
 
 fi
 AM_CONDITIONAL(BUILD_BSFILTER_PLUGIN, test x"$ac_cv_enable_bsfilter_plugin" = xyes)
 
+dnl --- clamd ---
+AC_MSG_CHECKING([whether to build clamd plugin])
+AC_ARG_ENABLE(clamd-plugin,
+       [  --disable-clamd-plugin      do not build clamd plugin],
+       [ac_cv_enable_clamd_plugin=$enableval], [ac_cv_enable_clamd_plugin=yes])
+if test x"$ac_cv_enable_clamd_plugin" = xyes; then
+       PLUGINS="clamd $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(BUILD_CLAMD_PLUGIN, test x"$ac_cv_enable_clamd_plugin" = xyes)
+
 dnl --- Demo ---
 AC_ARG_ENABLE(demo-plugin,
        [  --enable-demo-plugin              build demo plugin],
        [ac_cv_enable_demo_plugin=$enableval], [ac_cv_enable_demo_plugin=no])
 if test x"$ac_cv_enable_demo_plugin" = xyes; then
        PLUGINS="demo $PLUGINS"
 dnl --- Demo ---
 AC_ARG_ENABLE(demo-plugin,
        [  --enable-demo-plugin              build demo plugin],
        [ac_cv_enable_demo_plugin=$enableval], [ac_cv_enable_demo_plugin=no])
 if test x"$ac_cv_enable_demo_plugin" = xyes; then
        PLUGINS="demo $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
 fi
 AM_CONDITIONAL(BUILD_DEMO_PLUGIN, test x"$ac_cv_enable_demo_plugin" = xyes)
 
 fi
 AM_CONDITIONAL(BUILD_DEMO_PLUGIN, test x"$ac_cv_enable_demo_plugin" = xyes)
 
@@ -993,6 +1027,209 @@ if test x"$ac_cv_enable_fancy_plugin" = xyes; then
 fi
 AM_CONDITIONAL(BUILD_FANCY_PLUGIN, test x"$ac_cv_enable_fancy_plugin" = xyes)
 
 fi
 AM_CONDITIONAL(BUILD_FANCY_PLUGIN, test x"$ac_cv_enable_fancy_plugin" = xyes)
 
+dnl --- fetchinfo ---
+AC_MSG_CHECKING([whether to build fetchinfo plugin])
+AC_ARG_ENABLE(fetchinfo-plugin,
+       [  --disable-fetchinfo-plugin      do not build fetchinfo plugin],
+       [ac_cv_enable_fetchinfo_plugin=$enableval], [ac_cv_enable_fetchinfo_plugin=yes])
+if test x"$ac_cv_enable_fetchinfo_plugin" = xyes; then
+       PLUGINS="fetchinfo $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(BUILD_FETCHINFO_PLUGIN, test x"$ac_cv_enable_fetchinfo_plugin" = xyes)
+
+dnl --- gdata ---
+AC_MSG_CHECKING([whether to build gdata plugin])
+AC_ARG_ENABLE(gdata-plugin,
+       [  --disable-gdata-plugin      do not build gdata plugin],
+       [ac_cv_enable_gdata_plugin=$enableval], [ac_cv_enable_gdata_plugin=yes])
+if test x"$ac_cv_enable_gdata_plugin" = xyes; then
+       PKG_CHECK_MODULES(GDATA, libgdata >= 0.9.1, [ac_cv_enable_gdata_plugin=yes], [ac_cv_enable_gdata_plugin=no])
+       if test x"$ac_cv_enable_gdata_plugin" = xyes; then
+               AC_DEFINE(HAVE_GDATA_VERSION_0_9_1, 1, [at least version 0.9.1 of libgdata is available])
+               AC_DEFINE(HAVE_GDATA_VERSION_0_9, 1, [at least version 0.9.0 of libgdata is available])
+       else
+               PKG_CHECK_MODULES(GDATA, libgdata >= 0.6.4, [ac_cv_enable_gdata_plugin=yes], [ac_cv_enable_gdata_plugin=no])
+       fi
+       if test x"$ac_cv_enable_gdata_plugin" = xyes; then
+               AC_SUBST(GDATA_CFLAGS)
+               AC_SUBST(GDATA_LIBS)
+               AC_DEFINE(CM_GDATA_CLIENT_ID, ["Claws Mail GData plugin"], [client id])
+
+               PLUGINS="gdata $PLUGINS"
+               AC_MSG_RESULT(yes)
+       else
+               AC_MSG_ERROR(libgdata is not available)
+       fi
+else
+       AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(BUILD_GDATA_PLUGIN, test x"$ac_cv_enable_gdata_plugin" = xyes)
+
+dnl --- mailmbox ---
+AC_MSG_CHECKING([whether to build mailmbox plugin])
+AC_ARG_ENABLE(mailmbox-plugin,
+       [  --disable-mailmbox-plugin      do not build mailmbox plugin],
+       [ac_cv_enable_mailmbox_plugin=$enableval], [ac_cv_enable_mailmbox_plugin=yes])
+if test x"$ac_cv_enable_mailmbox_plugin" = xyes; then
+       PLUGINS="mailmbox $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(BUILD_MAILMBOX_PLUGIN, test x"$ac_cv_enable_mailmbox_plugin" = xyes)
+
+dnl --- newmail ---
+AC_MSG_CHECKING([whether to build newmail plugin])
+AC_ARG_ENABLE(newmail-plugin,
+       [  --disable-newmail-plugin      do not build newmail plugin],
+       [ac_cv_enable_newmail_plugin=$enableval], [ac_cv_enable_newmail_plugin=yes])
+if test x"$ac_cv_enable_newmail_plugin" = xyes; then
+       PLUGINS="newmail $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(BUILD_NEWMAIL_PLUGIN, test x"$ac_cv_enable_newmail_plugin" = xyes)
+
+dnl --- notification - disabled - too many autofoo annoyances for today ---
+AC_MSG_CHECKING([whether to build notification plugin])
+AC_ARG_ENABLE(notification-plugin,
+       [  --disable-notification-plugin      do not build notification plugin],
+       [ac_cv_enable_notification_plugin=$enableval], [ac_cv_enable_notification_plugin=no])
+if test x"$ac_cv_enable_notification_plugin" = xyes; then
+       PLUGINS="notification $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(BUILD_NOTIFICATION_PLUGIN, test x"$ac_cv_enable_notification_plugin" = xyes)
+
+dnl --- pdf_viewer ---
+AC_MSG_CHECKING([whether to build pdf_viewer plugin])
+AC_ARG_ENABLE(pdf_viewer-plugin,
+       [  --disable-pdf_viewer-plugin      do not build pdf_viewer plugin],
+       [ac_cv_enable_pdf_viewer_plugin=$enableval], [ac_cv_enable_pdf_viewer_plugin=yes])
+if test x"$ac_cv_enable_pdf_viewer_plugin" = xyes; then
+       PKG_CHECK_MODULES(POPPLER, poppler-glib >= 0.4.2, ,
+                                         AC_MSG_ERROR([Can't find Poppler >= 0.4.2 Glib wrapper]))
+       AC_SUBST(POPPLER_LIBS)
+       AC_SUBST(POPPLER_CFLAGS)
+
+       OLD_CFLAGS=$CFLAGS
+       CFLAGS="$POPPLER_CFLAGS $GTK_CFLAGS $GLIB_CFLAGS"
+       AC_CHECK_DECL(POPPLER_DEST_NAMED,
+               [AC_DEFINE([HAVE_POPPLER_DEST_NAMED], [], [Description])],
+               ,[#include <poppler-action.h>])
+       AC_CHECK_DECL(POPPLER_DEST_XYZ,
+               [AC_DEFINE([HAVE_POPPLER_DEST_XYZ], [], [Description])],
+               ,[#include <poppler-action.h>])
+       CFLAGS=$OLD_CFLAGS
+       PLUGINS="pdf_viewer $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(BUILD_PDF_VIEWER_PLUGIN, test x"$ac_cv_enable_pdf_viewer_plugin" = xyes)
+
+dnl --- perl ---
+AC_MSG_CHECKING([whether to build perl plugin])
+AC_ARG_ENABLE(perl-plugin,
+       [  --disable-perl-plugin      do not build perl plugin],
+       [ac_cv_enable_perl_plugin=$enableval], [ac_cv_enable_perl_plugin=yes])
+if test x"$ac_cv_enable_perl_plugin" = xyes; then
+       AC_PATH_PROG(sedpath, sed, no)
+       if test x$sedpath = xno ; then
+         AC_MSG_ERROR(Test for sed failed.)
+       fi
+
+       dnl Perl
+       AC_PATH_PROG(PERL_PATH, perl, no)
+       if test x$PERL_PATH = xno ; then
+         AC_MSG_ERROR(Test for Perl failed)
+       fi
+       AC_MSG_CHECKING(for perl >= 5.8.0)
+       PERL_VER=`$PERL_PATH -e 'print $] > 5.0079999?"yes":"no"'`
+       if test "$PERL_VER" = "yes"; then
+         AC_MSG_RESULT(yes)
+       else
+         AC_MSG_RESULT(no)
+         AC_MSG_ERROR(Your Perl version is too old.)
+       fi
+       AC_MSG_CHECKING(for Perl compile flags)
+       PERL_CFLAGS=`$PERL_PATH -MExtUtils::Embed -e ccopts`
+       PERL_CFLAGS=`echo $PERL_CFLAGS | $sedpath 's/-D_FILE_OFFSET_BITS=[[0-9]]*//'`
+       PERL_LDFLAGS=`$PERL_PATH -MExtUtils::Embed -e ldopts |$sedpath 's/-lgdbm//'`
+       PERL_LDFLAGS=`echo $PERL_LDFLAGS |$sedpath 's/-ldb//'`
+       PERL_LDFLAGS=`echo $PERL_LDFLAGS |$sedpath 's/-lndbm//'`
+       PERL_LDFLAGS=`echo $PERL_LDFLAGS |$sedpath 's/-lc//'`
+       AC_MSG_RESULT(ok)
+       AC_SUBST(PERL_CFLAGS)
+       AC_SUBST(PERL_LDFLAGS)
+       PLUGINS="perl $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(BUILD_PERL_PLUGIN, test x"$ac_cv_enable_perl_plugin" = xyes)
+
+dnl --- python ---
+AC_MSG_CHECKING([whether to build python plugin])
+AC_ARG_ENABLE(python-plugin,
+       [  --disable-python-plugin      do not build python plugin],
+       [ac_cv_enable_python_plugin=$enableval], [ac_cv_enable_python_plugin=yes])
+if test x"$ac_cv_enable_python_plugin" = xyes; then
+       AM_PATH_PYTHON([2.5], [
+           AC_PATH_PROG(PYTHON_CONFIG, python$PYTHON_VERSION-config)
+           if test x"$PYTHON_CONFIG" = x"" ; then
+               AC_PATH_PROG(PYTHON_CONFIG, python-config)
+           fi
+           if test x"$PYTHON_CONFIG" != x""; then
+               PYTHON_CFLAGS=`$PYTHON_CONFIG --includes`
+               PYTHON_LIBS=`$PYTHON_CONFIG --libs`
+               PYTHON_PREFIX=`$PYTHON_CONFIG --prefix`
+           else
+                  AC_MSG_ERROR(python-config not found. Maybe you need to install development packages for Python.)
+           fi
+
+           # libpython.so
+           PYTHON_SO_FILE="libpython${PYTHON_VERSION}.so"
+               found_libpython_so="no"
+               if test -f "$PYTHON_PREFIX/lib/$PYTHON_SO_FILE"; then
+                       found_libpython_so="yes"
+                       PYTHON_SHARED_LIB=`python -c "import os,sys; print os.path.basename(os.path.realpath(\"$PYTHON_PREFIX/lib/$PYTHON_SO_FILE\"))"`
+               fi
+               if test -f "$PYTHON_PREFIX/lib64/$PYTHON_SO_FILE"; then
+                       found_libpython_so="yes"
+                       PYTHON_SHARED_LIB=`python -c "import os,sys; print os.path.basename(os.path.realpath(\"$PYTHON_PREFIX/lib64/$PYTHON_SO_FILE\"))"`
+               fi
+               if test x"$found_libpython_so" != x"yes"; then
+                  AC_MSG_ERROR(Could not find Python shared libary: $PYTHON_SO_FILE does not exist. Maybe you need to install development packages for Python.)
+               fi
+
+               # PyGTK
+           PKG_CHECK_MODULES(PYGTK,
+                             [pygtk-2.0 >= 2.10.3],
+                             [
+                               AC_DEFINE(ENABLE_PYTHON, [1], [Enable Python support])
+                             ])
+       ])
+
+       AC_SUBST(PYTHON_SHARED_LIB)
+       AC_SUBST(PYTHON_CFLAGS)
+       AC_SUBST(PYTHON_LIBS)
+       AC_SUBST(PYGTK_CFLAGS)
+       AC_SUBST(PYGTK_LIBS)
+
+       PLUGINS="python $PLUGINS"
+       AC_MSG_RESULT(yes)
+else
+       AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(BUILD_PYTHON_PLUGIN, test x"$ac_cv_enable_python_plugin" = xyes)
+
 dnl --- PGP/CORE ---
 AC_MSG_CHECKING([whether to build PGP/CORE plugin])
 AC_ARG_ENABLE(pgpcore-plugin,
 dnl --- PGP/CORE ---
 AC_MSG_CHECKING([whether to build PGP/CORE plugin])
 AC_ARG_ENABLE(pgpcore-plugin,
@@ -1214,9 +1451,18 @@ src/plugins/att_remover/Makefile
 src/plugins/attachwarner/Makefile
 src/plugins/bogofilter/Makefile
 src/plugins/bsfilter/Makefile
 src/plugins/attachwarner/Makefile
 src/plugins/bogofilter/Makefile
 src/plugins/bsfilter/Makefile
+src/plugins/clamd/Makefile
+src/plugins/clamd/libclamd/Makefile
 src/plugins/demo/Makefile
 src/plugins/dillo_viewer/Makefile
 src/plugins/fancy/Makefile
 src/plugins/demo/Makefile
 src/plugins/dillo_viewer/Makefile
 src/plugins/fancy/Makefile
+src/plugins/fetchinfo/Makefile
+src/plugins/gdata/Makefile
+src/plugins/mailmbox/Makefile
+src/plugins/newmail/Makefile
+src/plugins/pdf_viewer/Makefile
+src/plugins/perl/Makefile
+src/plugins/python/Makefile
 src/plugins/pgpcore/Makefile
 src/plugins/pgpmime/Makefile
 src/plugins/pgpinline/Makefile
 src/plugins/pgpcore/Makefile
 src/plugins/pgpmime/Makefile
 src/plugins/pgpinline/Makefile
index 12c1e70285e0d52fc22592d0de30754110ddd18c..73850f2d541bfaa9b97ba05a13d51718848a3dd2 100644 (file)
@@ -26,6 +26,10 @@ if BUILD_BSFILTER_PLUGIN
 bsfilter_dir = bsfilter
 endif
 
 bsfilter_dir = bsfilter
 endif
 
+if BUILD_CLAMD_PLUGIN
+clamd_dir = clamd
+endif
+
 if BUILD_DEMO_PLUGIN
 demo_dir = demo
 endif
 if BUILD_DEMO_PLUGIN
 demo_dir = demo
 endif
@@ -38,6 +42,38 @@ if BUILD_FANCY_PLUGIN
 fancy_dir = fancy
 endif
 
 fancy_dir = fancy
 endif
 
+if BUILD_FETCHINFO_PLUGIN
+fetchinfo_dir = fetchinfo
+endif
+
+if BUILD_GDATA_PLUGIN
+gdata_dir = gdata
+endif
+
+if BUILD_MAILMBOX_PLUGIN
+mailmbox_dir = mailmbox
+endif
+
+if BUILD_NEWMAIL_PLUGIN
+newmail_dir = newmail
+endif
+
+if BUILD_NOTIFICATION_PLUGIN
+notification_dir = notification
+endif
+
+if BUILD_PDF_VIEWER_PLUGIN
+pdf_viewer_dir = pdf_viewer
+endif
+
+if BUILD_PERL_PLUGIN
+perl_dir = perl
+endif
+
+if BUILD_PYTHON_PLUGIN
+python_dir = python
+endif
+
 if BUILD_PGPCORE_PLUGIN
 pgpcore_dir = pgpcore
 if BUILD_PGPMIME_PLUGIN
 if BUILD_PGPCORE_PLUGIN
 pgpcore_dir = pgpcore
 if BUILD_PGPMIME_PLUGIN
@@ -75,9 +111,18 @@ SUBDIRS = \
        $(attachwarner_dir) \
        $(bogofilter_dir) \
        $(bsfilter_dir) \
        $(attachwarner_dir) \
        $(bogofilter_dir) \
        $(bsfilter_dir) \
+       $(clamd_dir) \
        $(demo_dir) \
        $(dillo_viewer_dir) \
        $(fancy_dir) \
        $(demo_dir) \
        $(dillo_viewer_dir) \
        $(fancy_dir) \
+       $(fetchinfo_dir) \
+       $(gdata_dir) \
+       $(mailmbox_dir) \
+       $(newmail_dir) \
+       $(notification_dir) \
+       $(pdf_viewer_dir) \
+       $(perl_dir) \
+       $(python_dir) \
        $(pgpcore_dir) \
        $(pgpmime_dir) \
        $(pgpinline_dir) \
        $(pgpcore_dir) \
        $(pgpmime_dir) \
        $(pgpinline_dir) \
index 5e1d1180edbf18485bd7cdad94b6c9fd6a898f7f..1bf70073b737d22f61ab6560294e2cfdda02bcaa 100644 (file)
@@ -1,8 +1,5 @@
 plugindir = $(pkglibdir)/plugins
 
 plugindir = $(pkglibdir)/plugins
 
-INCLUDES = @GLIB_CFLAGS@ \
-                  -I$(top_srcdir) -I$(top_builddir)
-
 plugin_LTLIBRARIES = archive.la
 
 archive_la_SOURCES = \
 plugin_LTLIBRARIES = archive.la
 
 archive_la_SOURCES = \
diff --git a/src/plugins/clamd/Makefile.am b/src/plugins/clamd/Makefile.am
new file mode 100644 (file)
index 0000000..f2fccb2
--- /dev/null
@@ -0,0 +1,30 @@
+SUBDIRS = libclamd
+plugindir = $(pkglibdir)/plugins
+
+plugin_LTLIBRARIES = clamd.la
+
+clamd_la_SOURCES = \
+       clamav_plugin.c \
+       clamav_plugin_gtk.c \
+       clamav_plugin.h
+
+clamd_la_LDFLAGS = \
+       -avoid-version -module \
+       $(GTK_LIBS) \
+       -L$(top_builddir)/src/plugins/clamd/libclamd
+
+clamd_la_LIBADD = \
+       -lclamd-plugin
+
+INCLUDES = \
+       -I$(top_srcdir)/src/plugins/clamd/libclamd \
+       -I$(top_srcdir)/src \
+       -I$(top_srcdir)/src/common \
+       -I$(top_srcdir)/src/gtk
+
+AM_CPPFLAGS = \
+       -Wall \
+       $(CLAWS_MAIL_CFLAGS) \
+       $(GLIB_CFLAGS) \
+       $(GTK_CFLAGS) \
+       -DLOCALEDIR=\""$(localedir)"\"
diff --git a/src/plugins/clamd/clamav_plugin.c b/src/plugins/clamd/clamav_plugin.c
new file mode 100644 (file)
index 0000000..03a8017
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2003-2010 Michael Rasmussen and the Claws Mail Team
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#  include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "defs.h"
+
+#include "common/claws.h"
+#include "common/version.h"
+#include "plugin.h"
+#include "utils.h"
+#include "hooks.h"
+#include "inc.h"
+#include "mimeview.h"
+#include "folder.h"
+#include "prefs.h"
+#include "prefs_gtk.h"
+#include "alertpanel.h"
+
+#include "clamav_plugin.h"
+#include "clamd-plugin.h"
+
+#define PLUGIN_NAME (_("Clam AntiVirus"))
+
+static guint hook_id;
+static MessageCallback message_callback;
+
+static ClamAvConfig config;
+
+static PrefParam param[] = {
+       {"clamav_enable", "FALSE", &config.clamav_enable, P_BOOL,
+        NULL, NULL, NULL},
+/*     {"clamav_enable_arc", "FALSE", &config.clamav_enable_arc, P_BOOL,
+        NULL, NULL, NULL},*/
+       {"clamav_max_size", "1", &config.clamav_max_size, P_USHORT,
+        NULL, NULL, NULL},
+       {"clamav_recv_infected", "TRUE", &config.clamav_recv_infected, P_BOOL,
+        NULL, NULL, NULL},
+       {"clamav_save_folder", NULL, &config.clamav_save_folder, P_STRING,
+        NULL, NULL, NULL},
+       {"clamad_config_type", "TRUE", &config.clamd_config_type, P_BOOL,
+        NULL, NULL, NULL},
+       {"clamd_config_folder", NULL, &config.clamd_config_folder, P_STRING,
+        NULL, NULL, NULL},
+       {"clamd_host", NULL, &config.clamd_host, P_STRING,
+        NULL, NULL, NULL},
+       {"clamd_port", NULL, &config.clamd_port, P_INT,
+        NULL, NULL, NULL},
+
+       {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
+};
+
+struct clamd_result {
+       Clamd_Stat status;
+};
+
+static gboolean scan_func(GNode *node, gpointer data)
+{
+       struct clamd_result *result = (struct clamd_result *) data;
+       MimeInfo *mimeinfo = (MimeInfo *) node->data;
+       gchar *outfile;
+       response buf;
+       int max;
+       struct stat info;
+
+       outfile = procmime_get_tmp_file_name(mimeinfo);
+       if (procmime_get_part(outfile, mimeinfo) < 0)
+               g_warning("Can't get the part of multipart message.");
+       else {
+       max = config.clamav_max_size * 1048576; /* maximum file size */
+               if (stat(outfile, &info) == -1)
+                       g_warning("Can't determine file size");
+               else {
+                       if (info.st_size <= max) {
+                               debug_print("Scanning %s\n", outfile);
+                               result->status = clamd_verify_email(outfile, &buf);
+                               debug_print("status: %d\n", result->status);
+                               switch (result->status) {
+                                       case NO_SOCKET: 
+                                               g_warning("[scanning] No socket information");
+                                               alertpanel_error(_("Scanning\nNo socket information.\nAntivirus disabled."));
+                                               break;
+                                       case NO_CONNECTION:
+                                               g_warning("[scanning] Clamd does not respond to ping");
+                                               alertpanel_warning(_("Scanning\nClamd does not respond to ping.\nIs clamd running?"));
+                                               break;
+                                       case VIRUS: 
+                                               g_warning("Detected %s virus.\n", clamd_get_virus_name(buf.msg));
+                                               alertpanel_warning(_("Detected %s virus."), clamd_get_virus_name(buf.msg));
+                                               break;
+                                       case SCAN_ERROR:
+                                               debug_print("Error: %s\n", buf.msg);
+                                               alertpanel_error(_("Scanning error:\n%s"), buf.msg);
+                                               break;
+                                       case OK:
+                                               debug_print("No virus detected.\n");
+                                               break;
+                               }
+                       }
+                       else {
+                               debug_print("File: %s. Size (%d) greater than limit (%d)\n",
+                                                       outfile, (int) info.st_size, max);
+                       }
+               }
+               g_unlink(outfile);
+       }
+
+       return (result->status == OK) ? FALSE : TRUE;
+}
+
+static gboolean mail_filtering_hook(gpointer source, gpointer data)
+{
+       MailFilteringData *mail_filtering_data = (MailFilteringData *) source;
+       MsgInfo *msginfo = mail_filtering_data->msginfo;
+       MimeInfo *mimeinfo;
+
+       struct clamd_result result;
+
+       if (!config.clamav_enable)
+               return FALSE;
+
+       mimeinfo = procmime_scan_message(msginfo);
+       if (!mimeinfo) return FALSE;
+
+       debug_print("Scanning message %d for viruses\n", msginfo->msgnum);
+       if (message_callback != NULL)
+               message_callback(_("ClamAV: scanning message..."));
+
+       debug_print("status: %d\n", result.status);
+       g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, scan_func, &result);
+
+       if (result.status == VIRUS) {
+               if (config.clamav_recv_infected) {
+                       FolderItem *clamav_save_folder;
+
+                       if ((!config.clamav_save_folder) ||
+                           (config.clamav_save_folder[0] == '\0') ||
+                           ((clamav_save_folder = folder_find_item_from_identifier(config.clamav_save_folder)) == NULL))
+                                   clamav_save_folder = folder_get_default_trash();
+
+                       procmsg_msginfo_unset_flags(msginfo, ~0, 0);
+                       msginfo->filter_op = IS_MOVE;
+                       msginfo->to_filter_folder = clamav_save_folder;
+               } else {
+                       folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
+               }
+       }
+       
+       procmime_mimeinfo_free_all(mimeinfo);
+       
+       return (result.status == OK) ? FALSE : TRUE;
+}
+
+Clamd_Stat clamd_prepare(void) {
+       debug_print("Creating socket\n");
+       if (!config.clamd_config_type || (config.clamd_host != NULL && config.clamd_port > 0)) {
+               if (config.clamd_host == NULL || config.clamd_port < 1) {
+                       /* error */
+                       return NO_SOCKET;
+               }
+               /* Manual configuration has highest priority */
+               debug_print("Using user input: %s:%d\n",
+                       config.clamd_host, config.clamd_port);
+               clamd_create_config_manual(config.clamd_host, config.clamd_port);
+       }
+       else if (config.clamd_config_type || config.clamd_config_folder != NULL) {
+               if (config.clamd_config_folder == NULL) {
+                       /* error */
+                       return NO_SOCKET;
+               }
+               debug_print("Using clamd.conf: %s\n", config.clamd_config_folder);
+               clamd_create_config_automatic(config.clamd_config_folder);
+       }
+       else {
+               /* Fall back. Try enable anyway */
+               if (! clamd_find_socket())
+                       return NO_SOCKET;
+       }
+
+       return clamd_init(NULL);
+}
+
+ClamAvConfig *clamav_get_config(void)
+{
+       return &config;
+}
+
+void clamav_save_config(void)
+{
+       PrefFile *pfile;
+       gchar *rcpath;
+
+       debug_print("Saving Clamd Page\n");
+
+       rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+       pfile = prefs_write_open(rcpath);
+       g_free(rcpath);
+       if (!pfile || (prefs_set_block_label(pfile, "ClamAV") < 0))
+               return;
+
+       if (prefs_write_param(param, pfile->fp) < 0) {
+               g_warning("failed to write Clamd configuration to file\n");
+               prefs_file_close_revert(pfile);
+               return;
+       }
+    if (fprintf(pfile->fp, "\n") < 0) {
+               FILE_OP_ERROR(rcpath, "fprintf");
+               prefs_file_close_revert(pfile);
+       } else
+               prefs_file_close(pfile);
+}
+
+void clamav_set_message_callback(MessageCallback callback)
+{
+       message_callback = callback;
+}
+
+gint plugin_init(gchar **error)
+{
+       gchar *rcpath;
+
+       if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
+                               VERSION_NUMERIC, PLUGIN_NAME, error))
+               return -1;
+
+       hook_id = hooks_register_hook(MAIL_FILTERING_HOOKLIST, mail_filtering_hook, NULL);
+       if (hook_id == -1) {
+               *error = g_strdup(_("Failed to register mail filtering hook"));
+               return -1;
+       }
+
+       prefs_set_default(param);
+       rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+       prefs_read_config(param, "ClamAV", rcpath, NULL);
+       g_free(rcpath);
+
+       clamav_gtk_init();
+
+       if (config.clamav_enable) {
+               debug_print("Creating socket\n");
+               Clamd_Stat status = clamd_prepare();
+               switch (status) {
+                       case NO_SOCKET: 
+                               g_warning("[init] No socket information");
+                               alertpanel_error(_("Init\nNo socket information.\nAntivirus disabled."));
+                               break;
+                       case NO_CONNECTION:
+                               g_warning("[init] Clamd does not respond to ping");
+                               alertpanel_warning(_("Init\nClamd does not respond to ping.\nIs clamd running?"));
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+       debug_print("Clamd plugin loaded\n");
+
+       return 0;
+       
+}
+
+gboolean plugin_done(void)
+{
+       hooks_unregister_hook(MAIL_FILTERING_HOOKLIST, hook_id);
+       g_free(config.clamav_save_folder);
+       clamav_gtk_done();
+       clamd_free();
+
+       debug_print("Clamd plugin unloaded\n");
+       return TRUE;
+}
+
+const gchar *plugin_name(void)
+{
+       return PLUGIN_NAME;
+}
+
+const gchar *plugin_desc(void)
+{
+       return _("This plugin uses Clam AntiVirus to scan all messages that are "
+              "received from an IMAP, LOCAL or POP account.\n"
+              "\n"
+              "When a message attachment is found to contain a virus it can be "
+              "deleted or saved in a specially designated folder.\n"
+              "\n"
+              "Because this plugin communicates with clamd via a\n"
+              "socket then there are some minimum requirements to\n"
+              "the permissions for your home folder and the\n"
+              ".claws-mail folder provided the clamav-daemon is\n"
+              "configured to communicate via a unix socket. All\n"
+              "users at least need to be given execute permissions\n"
+              "on these folders.\n"
+              "\n"
+              "To avoid changing permissions you could configure\n"
+              "the clamav-daemon to communicate via a TCP socket\n"
+              "and choose manual configuration for clamd.\n" 
+              "\n"
+              "Options can be found in /Configuration/Preferences/Plugins/Clam AntiVirus");
+}
+
+const gchar *plugin_type(void)
+{
+       return "GTK2";
+}
+
+const gchar *plugin_licence(void)
+{
+       return "GPL3+";
+}
+
+const gchar *plugin_version(void)
+{
+       return VERSION;
+}
+
+struct PluginFeature *plugin_provides(void)
+{
+       static struct PluginFeature features[] = 
+               { {PLUGIN_FILTERING, N_("Virus detection")},
+                 {PLUGIN_NOTHING, NULL}};
+       return features;
+}
diff --git a/src/plugins/clamd/clamav_plugin.h b/src/plugins/clamd/clamav_plugin.h
new file mode 100644 (file)
index 0000000..e6530af
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2003-2010 Michael Rasmussen and the Claws Mail Team
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+#ifndef CLAMAV_PLUGIN_H
+#define CLAMAV_PLUGIN_H 1
+
+#include <glib.h>
+#include "clamd-plugin.h"
+
+typedef struct _ClamAvConfig ClamAvConfig;
+
+typedef void (*MessageCallback) (gchar *);
+
+struct _ClamAvConfig
+{
+       gboolean         clamav_enable;
+/*     gboolean         clamav_enable_arc;*/
+       guint            clamav_max_size;
+       gboolean         clamav_recv_infected;
+       gchar           *clamav_save_folder;
+       gboolean         clamd_config_type;
+       gchar           *clamd_host;
+       int                      clamd_port;
+       gchar           *clamd_config_folder;
+};
+
+ClamAvConfig *clamav_get_config                  (void);
+void         clamav_save_config          (void);
+void         clamav_set_message_callback (MessageCallback callback);
+Clamd_Stat       clamd_prepare(void);
+gint         clamav_gtk_init(void);
+void         clamav_gtk_done(void);
+
+#endif
diff --git a/src/plugins/clamd/clamav_plugin_gtk.c b/src/plugins/clamd/clamav_plugin_gtk.c
new file mode 100644 (file)
index 0000000..f01555a
--- /dev/null
@@ -0,0 +1,569 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2003-2010 the Claws Mail Team
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#  include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "defs.h"
+
+#include <gtk/gtk.h>
+#include <gtk/gtkutils.h>
+
+#include "common/claws.h"
+#include "common/version.h"
+#include "plugin.h"
+#include "utils.h"
+#include "prefs.h"
+#include "folder.h"
+#include "prefs_gtk.h"
+#include "foldersel.h"
+#include "clamav_plugin.h"
+#include "statusbar.h"
+#include "alertpanel.h"
+#include "clamd-plugin.h"
+
+struct ClamAvPage
+{
+       PrefsPage page;
+       
+       GtkWidget *enable_clamav;
+/*     GtkWidget *enable_arc;*/
+       GtkWidget *max_size;
+       GtkWidget *recv_infected;
+       GtkWidget *save_folder;
+       GtkWidget *config_type;
+       GtkWidget *config_folder;
+       GtkWidget *config_host;
+       GtkWidget *config_port;
+};
+
+static GtkWidget *hbox_auto1, *hbox_auto2, *hbox_manual1, *hbox_manual2;
+
+static void foldersel_cb(GtkWidget *widget, gpointer data)
+{
+       struct ClamAvPage *page = (struct ClamAvPage *) data;
+       FolderItem *item;
+       gchar *item_id;
+       gint newpos = 0;
+       
+       item = foldersel_folder_sel(NULL, FOLDER_SEL_MOVE, NULL, FALSE);
+       if (item && (item_id = folder_item_get_identifier(item)) != NULL) {
+               gtk_editable_delete_text(GTK_EDITABLE(page->save_folder), 0, -1);
+               gtk_editable_insert_text(GTK_EDITABLE(page->save_folder), item_id, strlen(item_id), &newpos);
+               g_free(item_id);
+       }
+}
+
+static void clamd_folder_cb(GtkWidget *widget, gpointer data)
+{
+       GtkWidget *dialog;
+       gchar* file;
+       gint newpos = 0;
+       struct ClamAvPage *page = (struct ClamAvPage *) data;
+
+       dialog = gtk_file_chooser_dialog_new(
+                                       "Select file with clamd configuration [clamd.conf]",
+                                       NULL,
+                                       GTK_FILE_CHOOSER_ACTION_OPEN,
+                                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                                       GTK_STOCK_APPLY, GTK_RESPONSE_APPLY,
+                                       NULL);
+       gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), "/etc");
+       if (gtk_dialog_run (GTK_DIALOG(dialog)) == GTK_RESPONSE_APPLY) {
+               file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+               debug_print("New clamd.conf: %s\n", file);
+               if (file) {
+                       gtk_editable_delete_text(GTK_EDITABLE(page->config_folder), 0, -1);
+                       gtk_editable_insert_text(GTK_EDITABLE(page->config_folder), file, strlen(file), &newpos);
+                       g_free(file);
+               }
+       }
+       gtk_widget_destroy(dialog);
+}
+
+static void check_permission(gchar* folder) {
+       struct stat info;
+
+       g_stat(folder, &info);
+       mode_t perm = info.st_mode & ~(S_IFMT);
+       debug_print("%s: Old file permission: %05o\n", folder, perm);
+       if ((perm & S_IXOTH) != S_IXOTH) {
+               perm = perm | S_IXOTH;
+               g_chmod(folder, perm);
+       }
+       debug_print("%s: New file permission: %05o\n", folder, perm);
+}
+
+static void folder_permission_cb(GtkWidget *widget, gpointer data) {
+       static gchar* folders[] = {
+                       ".claws-mail",
+                       ".claws-mail/mimetmp",
+                       ".claws-mail/tmp",
+                       NULL};
+       const gchar* home = g_get_home_dir();
+       int i;
+
+       check_permission((gchar *) home);
+       for (i = 0; folders[i]; i++) {
+               gchar* file = g_strdup_printf("%s/%s", home, folders[i]);
+               check_permission(file);
+               g_free(file);
+       }
+}
+
+static void clamav_show_config(Config* config) {
+       if (config) {
+               if (config->ConfigType == MANUAL) {
+                       gtk_widget_hide(hbox_auto1);
+                       gtk_widget_hide(hbox_auto2);
+                       gtk_widget_show(hbox_manual1);
+                       gtk_widget_show(hbox_manual2);
+               }
+               else {
+                       gtk_widget_hide(hbox_manual1);
+                       gtk_widget_hide(hbox_manual2);
+                       gtk_widget_show(hbox_auto1);
+                       gtk_widget_show(hbox_auto2);
+               }
+       }
+}
+
+static void setting_type_cb(GtkWidget *widget, gpointer data) {
+       gboolean state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+       struct ClamAvPage *page = (struct ClamAvPage *) data;
+       Config* c;
+       gint newpos = 0;
+       gboolean tmp_conf = FALSE;
+
+       if (page && page->page.widget) {
+               /* Reset configuration */
+               debug_print("Resetting configuration\n");
+               gtk_editable_delete_text(GTK_EDITABLE(page->config_folder), 0, -1);
+               gtk_editable_delete_text(GTK_EDITABLE(page->config_host), 0, -1);
+               gtk_editable_delete_text(GTK_EDITABLE(page->config_port), 0, -1);
+               clamav_save_config();
+       
+               c = clamd_get_config();
+               if (!c) {
+                       c = clamd_config_new();
+                       tmp_conf = TRUE;
+               }
+               if (state) {
+                       /* Automatic configuration */
+                       debug_print("Setting clamd to automatic configuration\n");
+                       if (clamd_find_socket()) {
+                               if (tmp_conf) {
+                                       Config* clamd_conf = clamd_get_config();
+                                       if (clamd_conf->automatic.folder)
+                                               c->automatic.folder = g_strdup(clamd_conf->automatic.folder);
+                                       else
+                                               c->automatic.folder = g_strdup("");
+                               }
+                               if (c->ConfigType == AUTOMATIC) {
+                                       gtk_editable_insert_text(GTK_EDITABLE(page->config_folder),
+                                               c->automatic.folder, strlen(c->automatic.folder), &newpos);
+                                       clamav_save_config();
+                               }
+                       }
+                       c->ConfigType = AUTOMATIC;
+                       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(page->config_type), TRUE);
+               }
+               else {
+                       /* Manual configuration */
+                       debug_print("Setting clamd to manual configuration\n");
+                       c->ConfigType = MANUAL;
+                       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(page->config_type), FALSE);
+               }
+               clamav_show_config(c);
+               if (tmp_conf)
+                       clamd_config_free(c);
+       }
+}
+
+static void clamav_create_widget_func(PrefsPage * _page, GtkWindow *window, gpointer data)
+{
+       struct ClamAvPage *page = (struct ClamAvPage *) _page;
+       ClamAvConfig *config;
+       Config           *clamd_config;
+        
+       GtkWidget *vbox1, *vbox2;
+       GtkWidget *enable_clamav;
+       GtkWidget *label1;
+/*     GtkWidget *enable_arc;*/
+       GtkWidget *label2;
+       GtkObject *max_size_adj;
+       GtkWidget *max_size;
+       GtkWidget *hbox1;
+       GtkWidget *recv_infected;
+       GtkWidget *save_folder;
+       GtkWidget *save_folder_select;
+       GtkWidget *clamd_conf_label;
+       GtkWidget *config_folder;
+       GtkWidget *config_host;
+       GtkWidget *config_port;
+       GtkWidget *config_folder_select;
+       GtkWidget *blank;
+       GtkWidget *permission_label;
+       GtkWidget *permission_select;
+       GtkWidget *host_label;
+       GtkWidget *port_label;
+       GtkWidget *setting_type;
+       GtkTooltips *tooltips;
+
+       tooltips = gtk_tooltips_new();
+       enable_clamav = page->enable_clamav;
+
+       vbox1 = gtk_vbox_new (FALSE, VSPACING);
+       gtk_widget_show (vbox1);
+       gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER);
+
+       vbox2 = gtk_vbox_new (FALSE, 4);
+       gtk_widget_show (vbox2);
+       gtk_box_pack_start (GTK_BOX (vbox1), vbox2, FALSE, FALSE, 0);
+
+       PACK_CHECK_BUTTON (vbox2, enable_clamav, _("Enable virus scanning"));
+/*     PACK_CHECK_BUTTON (vbox2, enable_arc, _("Scan archive contents"));
+
+       SET_TOGGLE_SENSITIVITY (enable_clamav, enable_arc);*/
+
+       hbox1 = gtk_hbox_new (FALSE, 8);
+       gtk_widget_show (hbox1);
+       gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0);
+/*     SET_TOGGLE_SENSITIVITY (enable_arc, hbox1);*/
+
+       label1 = gtk_label_new(_("Maximum attachment size"));
+       gtk_widget_show (label1);
+       gtk_box_pack_start (GTK_BOX (hbox1), label1, FALSE, FALSE, 0);
+       SET_TOGGLE_SENSITIVITY (enable_clamav, label1);
+
+       max_size_adj = gtk_adjustment_new (1, 1, 1024, 1, 10, 0);
+       max_size = gtk_spin_button_new (GTK_ADJUSTMENT (max_size_adj), 1, 0);
+       gtk_widget_show (max_size);
+       gtk_box_pack_start (GTK_BOX (hbox1), max_size, FALSE, FALSE, 0);
+       gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (max_size), TRUE);
+       gtk_tooltips_set_tip(tooltips, max_size,
+                            _("Message attachments larger than this will not be scanned"),
+                            NULL);
+       SET_TOGGLE_SENSITIVITY (enable_clamav, max_size);
+
+       label2 = gtk_label_new(_("MB"));
+       gtk_widget_show (label2);
+       gtk_box_pack_start (GTK_BOX (hbox1), label2, FALSE, FALSE, 0);
+       SET_TOGGLE_SENSITIVITY (enable_clamav, label2);
+
+       hbox1 = gtk_hbox_new (FALSE, 8);
+       gtk_widget_show (hbox1);
+       gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0);
+
+       recv_infected = gtk_check_button_new_with_label(_("Save infected mail in"));
+       gtk_widget_show (recv_infected);
+       gtk_box_pack_start (GTK_BOX (hbox1), recv_infected, FALSE, FALSE, 0);
+       gtk_tooltips_set_tip(tooltips, recv_infected,
+                            _("Save mail that contains viruses"),
+                            NULL);
+       SET_TOGGLE_SENSITIVITY (enable_clamav, recv_infected);
+
+       save_folder = gtk_entry_new ();
+       gtk_widget_show (save_folder);
+       gtk_box_pack_start (GTK_BOX (hbox1), save_folder, TRUE, TRUE, 0);
+       gtk_tooltips_set_tip(tooltips, save_folder,
+                            _("Folder for storing infected mail. Leave empty to use the default trash folder"),
+                            NULL);
+       SET_TOGGLE_SENSITIVITY (enable_clamav, save_folder);
+
+       save_folder_select = gtkut_get_browse_directory_btn(_("_Browse"));
+       gtk_widget_show (save_folder_select);
+       gtk_box_pack_start (GTK_BOX (hbox1), save_folder_select, FALSE, FALSE, 0);
+       gtk_tooltips_set_tip(tooltips, save_folder_select,
+                            _("Click this button to select a folder for storing infected mail"),
+                            NULL);
+       SET_TOGGLE_SENSITIVITY (enable_clamav, save_folder_select);
+
+       hbox1 = gtk_hbox_new (FALSE, 8);
+       gtk_widget_show (hbox1);
+       gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0);
+
+       setting_type = gtk_check_button_new_with_label(_("Automatic configuration"));
+       /*gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(setting_type), TRUE);*/
+       gtk_widget_show (setting_type);
+       gtk_box_pack_start (GTK_BOX (hbox1), setting_type, FALSE, FALSE, 0);
+       gtk_tooltips_set_tip(tooltips, setting_type,
+                            _("Should configuration be done automatic or manual"),
+                            NULL);
+       SET_TOGGLE_SENSITIVITY (enable_clamav, setting_type);
+       
+       hbox_auto1 = gtk_hbox_new (FALSE, 8);
+       gtk_widget_show (hbox_auto1);
+       gtk_box_pack_start (GTK_BOX (vbox2), hbox_auto1, FALSE, FALSE, 0);
+
+       clamd_conf_label = gtk_label_new(_("Where is clamd.conf"));
+       gtk_widget_show (clamd_conf_label);
+       gtk_box_pack_start (GTK_BOX (hbox_auto1), clamd_conf_label, FALSE, FALSE, 0);
+
+       config_folder = gtk_entry_new ();
+       gtk_widget_show (config_folder);
+       gtk_box_pack_start (GTK_BOX (hbox_auto1), config_folder, TRUE, TRUE, 0);
+       gtk_tooltips_set_tip(tooltips, config_folder,
+                            _("Full path to clamd.conf. If this field is not empty then the plugin has been able to locate the file automatically"),
+                            NULL);
+       SET_TOGGLE_SENSITIVITY (enable_clamav, config_folder);
+
+       config_folder_select = gtkut_get_browse_directory_btn(_("Br_owse"));
+       gtk_widget_show (config_folder_select);
+       gtk_box_pack_start (GTK_BOX (hbox_auto1), config_folder_select, FALSE, FALSE, 0);
+       gtk_tooltips_set_tip(tooltips, config_folder_select,
+                            _("Click this button to select full path to clamd.conf"),
+                            NULL);
+       SET_TOGGLE_SENSITIVITY (enable_clamav, config_folder_select);
+
+       hbox_auto2 = gtk_hbox_new (FALSE, 8);
+       gtk_widget_show (hbox_auto2);
+       gtk_box_pack_start (GTK_BOX (vbox2), hbox_auto2, FALSE, FALSE, 0);
+
+       permission_label = gtk_label_new(_("Check permission for folders and adjust if necessary"));
+       gtk_widget_show (permission_label);
+       gtk_box_pack_start (GTK_BOX (hbox_auto2), permission_label, FALSE, FALSE, 0);
+
+       blank = gtk_label_new("");
+       gtk_widget_show (blank);
+       gtk_box_pack_start (GTK_BOX (hbox_auto2), blank, TRUE, TRUE, 0);
+
+       permission_select = gtk_button_new_from_stock(GTK_STOCK_FIND_AND_REPLACE);
+                       /*gtk_button_new_with_mnemonic(_("_Check Permission"));*/
+       gtk_widget_show (permission_select);
+       gtk_box_pack_start (GTK_BOX (hbox_auto2), permission_select, FALSE, FALSE, 0);
+       gtk_tooltips_set_tip(tooltips, permission_select,
+                            _("Click this button to check and adjust folder permissions"),
+                            NULL);
+       SET_TOGGLE_SENSITIVITY (enable_clamav, permission_select);
+
+       hbox_manual1 = gtk_hbox_new (FALSE, 8);
+       gtk_widget_show (hbox_manual1);
+       gtk_box_pack_start (GTK_BOX (vbox2), hbox_manual1, FALSE, FALSE, 0);
+
+       host_label = gtk_label_new(_("Remote Host"));
+       gtk_widget_show (host_label);
+       gtk_box_pack_start (GTK_BOX (hbox_manual1), host_label, FALSE, FALSE, 0);
+
+       config_host = gtk_entry_new ();
+       gtk_widget_show (config_host);
+       gtk_box_pack_start (GTK_BOX (hbox_manual1), config_host, FALSE, FALSE, 0);
+       gtk_tooltips_set_tip(tooltips, config_host,
+                            _("Hostname or IP for remote host running clamav daemon"),
+                            NULL);
+       SET_TOGGLE_SENSITIVITY (enable_clamav, config_host);
+
+       blank = gtk_label_new("");
+       gtk_widget_show (blank);
+       gtk_box_pack_start (GTK_BOX (hbox_manual1), blank, TRUE, TRUE, 0);
+
+       hbox_manual2 = gtk_hbox_new (FALSE, 8);
+       gtk_widget_show (hbox_manual2);
+       gtk_box_pack_start (GTK_BOX (vbox2), hbox_manual2, FALSE, FALSE, 0);
+
+       port_label = gtk_label_new(_("Port"));
+       gtk_widget_show (port_label);
+       gtk_box_pack_start (GTK_BOX (hbox_manual2), port_label, FALSE, FALSE, 0);
+
+       config_port = gtk_entry_new ();
+       gtk_entry_set_width_chars(GTK_ENTRY(config_port), 5);
+       gtk_entry_set_max_length(GTK_ENTRY(config_port), 5);
+       gtk_widget_show (config_port);
+       gtk_box_pack_start (GTK_BOX (hbox_manual2), config_port, FALSE, FALSE, 0);
+       gtk_tooltips_set_tip(tooltips, config_port,
+                            _("Port number where clamav daemon is listening"),
+                            NULL);
+
+       blank = gtk_label_new("");
+       gtk_widget_show (blank);
+       gtk_box_pack_start (GTK_BOX (hbox_manual2), blank, TRUE, TRUE, 0);
+
+       SET_TOGGLE_SENSITIVITY (enable_clamav, config_port);
+
+       g_signal_connect(G_OBJECT(save_folder_select), "clicked", 
+                        G_CALLBACK(foldersel_cb), page);
+       g_signal_connect(G_OBJECT(config_folder_select), "clicked",
+                        G_CALLBACK(clamd_folder_cb), page);
+       g_signal_connect(G_OBJECT(permission_select), "clicked",
+                        G_CALLBACK(folder_permission_cb), page);
+       g_signal_connect(G_OBJECT(setting_type), "clicked",
+                        G_CALLBACK(setting_type_cb), page);
+
+       config = clamav_get_config();
+
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enable_clamav), config->clamav_enable);
+/*     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enable_arc), config->clamav_enable_arc);*/
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(max_size), (float) config->clamav_max_size);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(recv_infected), config->clamav_recv_infected);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(setting_type), config->clamd_config_type);
+
+       clamd_config = clamd_get_config();
+               
+       if (config->clamav_save_folder != NULL)
+               gtk_entry_set_text(GTK_ENTRY(save_folder), config->clamav_save_folder);
+       if (!config->clamd_config_type) {
+       /*if (config->clamd_host && strlen(config->clamd_host) > 0 && config->clamd_port > 0) {*/
+               gtk_entry_set_text(GTK_ENTRY(config_host), config->clamd_host);
+               gchar* s = int2char(config->clamd_port);
+               gtk_entry_set_text(GTK_ENTRY(config_port), s);
+               g_free(s);
+               /* activate manual checkbox and blind folder */
+               debug_print("Showing manual configuration and hiding automatic configuration\n");
+               if (! clamd_config) {
+                       clamd_config = clamd_config_new();
+                       clamd_config->ConfigType = MANUAL;
+                       clamav_show_config(clamd_config);
+                       clamd_config_free(clamd_config);
+               }
+               else
+                       clamav_show_config(clamd_config);
+       }
+       else {
+       //else if (config->clamd_config_folder == NULL || strlen(config->clamd_config_folder) == 0) {
+               if (clamd_find_socket()) {
+                       Config* c = clamd_get_config();
+                       if (c && c->ConfigType == AUTOMATIC) {
+                               config->clamd_config_folder = g_strdup(c->automatic.folder);
+                               /* deactivate manual checkbox and blind host and port */
+                               debug_print("Showing automatic configuration and hiding manual configuration\n");
+                               clamav_show_config(c);
+                               gint newpos = 0;
+                               gtk_editable_delete_text(GTK_EDITABLE(config_folder), 0, -1);
+                               gtk_editable_insert_text(GTK_EDITABLE(config_folder), 
+                                       config->clamd_config_folder, strlen(config->clamd_config_folder), &newpos);
+                       }
+                       else if (c && c->ConfigType == MANUAL) {
+                               /* deactivate automatic automatic configuration */
+                               debug_print("Showing manual configuration and hiding automatic configuration\n");
+                               clamav_show_config(c);
+                       }
+               }
+       }
+/*     else {
+               gtk_entry_set_text(GTK_ENTRY(config_folder), config->clamd_config_folder);
+               // deactivate manual checkbox and blind host and port
+               debug_print("Showing automatic configuration and hiding manual configuration\n");
+               if (! clamd_config) {
+                       clamd_config = clamd_config_new();
+                       clamd_config->ConfigType = AUTOMATIC;
+                       clamav_show_config(clamd_config);
+                       clamd_config_free(clamd_config);
+               }
+               else
+                       clamav_show_config(clamd_config);
+       }*/
+
+       page->enable_clamav = enable_clamav;
+/*     page->enable_arc = enable_arc;*/
+       page->max_size = max_size;
+       page->recv_infected = recv_infected;
+       page->save_folder = save_folder;
+       page->config_type = setting_type;
+       page->config_folder = config_folder;
+       page->config_host = config_host;
+       page->config_port = config_port;
+       page->page.widget = vbox1;
+       
+       clamav_save_config();
+}
+
+static void clamav_destroy_widget_func(PrefsPage *_page)
+{
+       debug_print("Destroying Clamd widget\n");
+}
+
+static void clamav_save_func(PrefsPage *_page)
+{
+       struct ClamAvPage *page = (struct ClamAvPage *) _page;
+       ClamAvConfig *config;
+
+       debug_print("Saving Clamd Page\n");
+
+       config = clamav_get_config();
+
+       config->clamav_enable = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->enable_clamav));
+/*     config->clamav_enable_arc = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->enable_arc));*/
+
+       config->clamav_max_size = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(page->max_size));
+       config->clamav_recv_infected = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->recv_infected));
+       g_free(config->clamav_save_folder);
+       config->clamav_save_folder = gtk_editable_get_chars(GTK_EDITABLE(page->save_folder), 0, -1);
+       config->clamd_config_type = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->config_type));
+       g_free(config->clamd_config_folder);
+       config->clamd_config_folder = gtk_editable_get_chars(GTK_EDITABLE(page->config_folder), 0, -1);
+       g_free(config->clamd_host);
+       config->clamd_host = gtk_editable_get_chars(GTK_EDITABLE(page->config_host), 0, -1);
+       config->clamd_port = atoi(gtk_entry_get_text(GTK_ENTRY(page->config_port)));
+
+       if (config->clamav_enable) {
+               Clamd_Stat status = clamd_prepare();
+               switch (status) {
+                       case NO_SOCKET: 
+                               g_warning("[New config] No socket information");
+                               alertpanel_error(_("New config\nNo socket information.\nAntivirus disabled."));
+                               break;
+                       case NO_CONNECTION:
+                               g_warning("[New config] Clamd does not respond to ping");
+                               alertpanel_warning(_("New config\nClamd does not respond to ping.\nIs clamd running?"));
+                               break;
+                       default:
+                               break;
+               }
+       }
+       clamav_save_config();
+}
+
+static struct ClamAvPage clamav_page;
+
+static void gtk_message_callback(gchar *message)
+{
+       statusbar_print_all("%s", message);
+}
+
+gint clamav_gtk_init(void)
+{
+       static gchar *path[3];
+
+       path[0] = _("Plugins");
+       path[1] = _("Clam AntiVirus");
+       path[2] = NULL;
+
+       clamav_page.page.path = path;
+       clamav_page.page.create_widget = clamav_create_widget_func;
+       clamav_page.page.destroy_widget = clamav_destroy_widget_func;
+       clamav_page.page.save_page = clamav_save_func;
+       clamav_page.page.weight = 35.0;
+       
+       prefs_gtk_register_page((PrefsPage *) &clamav_page);
+       clamav_set_message_callback(gtk_message_callback);
+
+       debug_print("Clamd GTK plugin loaded\n");
+       return 0;       
+}
+
+void clamav_gtk_done(void)
+{
+        prefs_gtk_unregister_page((PrefsPage *) &clamav_page);
+}
diff --git a/src/plugins/clamd/libclamd/Makefile.am b/src/plugins/clamd/libclamd/Makefile.am
new file mode 100644 (file)
index 0000000..38e8de8
--- /dev/null
@@ -0,0 +1,20 @@
+INCLUDES = @GLIB_CFLAGS@ \
+                       @GTK_CFLAGS@ \
+                       -I$(top_srcdir) \
+                       -I$(top_builddir) \
+                       $(CLAWS_MAIL_CFLAGS) \
+               -I$(top_srcdir)/src \
+               -I$(top_srcdir)/src/common \
+               -I$(top_srcdir)/src/gtk
+
+noinst_LTLIBRARIES = libclamd-plugin.la
+
+libclamd_plugin_la_SOURCES = \
+                     clamd-plugin.h \
+                     clamd-plugin.c
+
+noinst_HEADERS = clamd-plugin.h
+
+libclamd_plugin_la_LIBADD = \
+                               @GLIB_LIBS@ \
+                               @GTK_LIBS@
diff --git a/src/plugins/clamd/libclamd/clamd-plugin.c b/src/plugins/clamd/libclamd/clamd-plugin.c
new file mode 100644 (file)
index 0000000..63f8d1e
--- /dev/null
@@ -0,0 +1,689 @@
+/* vim: set textwidth=80 tabstop=4: */
+
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2008 Michael Rasmussen and the Claws Mail Team
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#      include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gtk/gtkutils.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "common/claws.h"
+#include "common/version.h"
+#include "plugin.h"
+#include "utils.h"
+#include "prefs.h"
+#include "folder.h"
+#include "prefs_gtk.h"
+#include "foldersel.h"
+#include "statusbar.h"
+#include "alertpanel.h"
+#include "clamd-plugin.h"
+
+/* needs to be generic */
+static const gchar* config_dirs[] = { 
+       "/etc", 
+       "/usr/local/etc",
+       "/etc/clamav",
+       "/usr/local/etc/clamav",
+       NULL };
+
+static const gchar* clamd_tokens[] = {
+       "LocalSocket",
+       "TCPSocket",
+       "TCPAddr",
+       NULL };
+
+static Clamd_Socket* Socket = NULL;
+static int sock;
+static Config* config = NULL;
+
+/**
+ *  clamd commands used
+ *  prefixing with either z or n is recommended
+ *  z <=> null terminated command
+ *  n <=> newline terminated command
+ */
+static const gchar ping[] = "nPING\n";
+static const gchar version[] = "nVERSION\n";
+static const gchar scan[] = "nSCAN";
+static const gchar contscan[] = "nCONTSCAN";
+static const gchar instream[10] = "zINSTREAM\0";
+
+void clamd_create_config_automatic(const gchar* path) {
+       FILE* conf;
+       char buf[1024];
+       gchar* key = NULL;
+       gchar* value = NULL;
+
+       /*debug_set_mode(TRUE);*/
+       /*debug_print("%s : %s\n", folder, path);*/
+       if (! path) {
+               g_warning("Missing path");
+               return;
+       }
+       if (config && config->ConfigType == AUTOMATIC &&
+                       config->automatic.folder &&
+                       strcmp(config->automatic.folder, path) == 0) {
+               debug_print("%s : %s - Identical. No need to read again\n",
+                       config->automatic.folder, path);
+               return;
+       }
+       if (config)
+               clamd_config_free(config);
+       config = clamd_config_new();
+       
+       config->ConfigType = AUTOMATIC;
+       config->automatic.folder = g_strdup(path);
+       debug_print("Opening %s to parse config file\n", path);
+       conf = fopen(path, "r");
+       if (!conf) {
+               /*g_error("%s: Unable to open", path);*/
+               alertpanel_error(_("%s: Unable to open\nclamd will be disabled"), path);
+               return;
+       }
+       while (fgets(buf, sizeof(buf), conf)) {
+               g_strstrip(buf);
+               if (buf[0] == '#')
+                       continue;
+               const gchar** tokens = clamd_tokens;
+               while (*tokens) {
+                       const gchar* token = *tokens++;
+                       if ((key = g_strstr_len(buf, strlen(buf), token)) != NULL) {
+                               gchar* tmp = &(*(key + strlen(token)));
+                               tmp = g_strchug(tmp);
+                               gchar* end = index(tmp, '#');
+                               if (end)
+                                       value = g_strndup(tmp, end - tmp);
+                               else
+                                       value = g_strdup(g_strchomp(tmp));
+                               if (strcmp(clamd_tokens[0], token) == 0) {
+                                       /* UNIX socket */
+                                       Socket = (Clamd_Socket *) malloc(sizeof(Clamd_Socket *));
+                                       if (Socket) {
+                                               Socket->socket.path = NULL;
+                                               Socket->socket.host = NULL;
+                                               Socket->socket.port = -1;
+                                               Socket->type = UNIX_SOCKET;
+                                               Socket->socket.path = g_strdup(value);
+                                               g_free(value);
+                                               value = NULL;
+                                               fclose(conf);
+                                               debug_print("clamctl: %s\n", Socket->socket.path);
+                                               return;
+                                       }
+                               }
+                               else if (strcmp(clamd_tokens[1], token) == 0) {
+                                       /* INET socket */
+                                       if (! Socket) {
+                                               Socket = (Clamd_Socket *) malloc(sizeof(Clamd_Socket *));
+                                               if (Socket) {
+                                                       Socket->socket.path = NULL;
+                                                       Socket->socket.host = NULL;
+                                                       Socket->socket.port = -1;
+                                                       Socket->type = INET_SOCKET;
+                                                       Socket->socket.port = atoi(value);
+                                                       Socket->socket.host = g_strdup("localhost");
+                                                       g_free(value);
+                                                       value = NULL;
+                                                       debug_print("clamctl: %s:%d\n", 
+                                                               Socket->socket.host, Socket->socket.port);
+                                               }
+                                       }
+                                       else {
+                                               Socket->type = INET_SOCKET;
+                                               Socket->socket.port = atoi(value);
+                                               g_free(value);
+                                               value = NULL;
+                                               if (! Socket->socket.host)
+                                                       Socket->socket.host = g_strdup("localhost");
+                                               debug_print("clamctl: %s:%d\n", 
+                                                       Socket->socket.host, Socket->socket.port);
+                                       }
+                                       /* We must continue since TCPAddr could also be configured */
+                               }
+                               else if (strcmp(clamd_tokens[2], token) == 0) {
+                                       if (! Socket) {
+                                               Socket = (Clamd_Socket *) malloc(sizeof(Clamd_Socket *));
+                                               if (Socket) {
+                                                       Socket->socket.path = NULL;
+                                                       Socket->socket.host = NULL;
+                                                       Socket->socket.port = 3310; /* default port */
+                                                       Socket->type = INET_SOCKET;
+                                                       Socket->socket.host = g_strdup(value);
+                                                       g_free(value);
+                                                       value = NULL;
+                                                       debug_print("clamctl: %s:%d\n", 
+                                                               Socket->socket.host, Socket->socket.port);
+                                               }
+                                       }
+                                       else {
+                                               Socket->type = INET_SOCKET;
+                                               if (Socket->socket.host)
+                                                       g_free(Socket->socket.host);
+                                               Socket->socket.host = g_strdup(value);
+                                               g_free(value);
+                                               value = NULL;
+                                               if (Socket->socket.port == -1)
+                                                       Socket->socket.port = 3310;
+                                               debug_print("clamctl: %s:%d\n", 
+                                                       Socket->socket.host, Socket->socket.port);
+                                       }
+                                       /* We must continue since TCPSocket could also be configured */
+                               }
+                       }
+               }
+       }
+       fclose(conf);
+       if (! (Socket && (Socket->socket.port || Socket->socket.path))) {
+               /*g_error("%s: Not able to find required information", path);*/
+               alertpanel_error(_("%s: Not able to find required information\nclamd will be disabled"), path);
+       }
+       /*debug_set_mode(FALSE);*/
+}
+
+void clamd_create_config_manual(const gchar* host, int port) {
+       if (! host || port < 1) {
+               g_warning("Missing host or port < 1");
+               return;
+       }
+       if (config && config->ConfigType == MANUAL &&
+                       config->manual.host && config->manual.port == port &&
+                       strcmp(config->manual.host, host) == 0) {
+               debug_print("%s : %s and %d : %d - Identical. No need to read again\n",
+                       config->manual.host, host, config->manual.port, port);
+               return;
+       }
+
+       if (config)
+               clamd_config_free(config);
+       config = clamd_config_new();
+       
+       config->ConfigType = MANUAL;
+       config->manual.host = g_strdup(host);
+       config->manual.port = port;
+       /* INET socket */
+       Socket = (Clamd_Socket *) malloc(sizeof(Clamd_Socket *));
+       if (Socket) {
+               Socket->type = INET_SOCKET;
+               Socket->socket.port = port;
+               Socket->socket.host = g_strdup(host);
+       }
+       else {
+               /*g_error("%s: Not able to find required information", path);*/
+               alertpanel_error(_("Could not create socket"));
+       }
+}
+
+gboolean clamd_find_socket() {
+       const gchar** config_dir = config_dirs;
+       gchar *clamd_conf = NULL;
+       
+       while (*config_dir) {
+               clamd_conf = g_strdup_printf("%s/clamd.conf", *config_dir++);
+               debug_print("Looking for %s\n", clamd_conf);
+               if (g_file_test(clamd_conf, G_FILE_TEST_EXISTS))
+                       break;
+               g_free(clamd_conf);
+               clamd_conf = NULL;
+       }
+       if (! clamd_conf)
+               return FALSE;
+
+       debug_print("Using %s to find configuration\n", clamd_conf);
+       clamd_create_config_automatic(clamd_conf);
+       g_free(clamd_conf);
+
+       return TRUE;
+}
+
+Config* clamd_get_config() {
+       return config;
+}
+
+Clamd_Socket* clamd_get_socket() {
+       return Socket;
+}
+
+static void close_socket() {
+       debug_print("Closing socket: %d\n", sock);
+       close(sock);
+}
+
+static void create_socket() {
+       struct sockaddr_un addr_u;
+       struct sockaddr_in addr_i;
+       struct hostent *hp;
+
+       /*debug_set_mode(TRUE);*/
+       if (! Socket) {
+               sock = -1;
+               return;
+       }
+       memset(&addr_u, 0, sizeof(addr_u));
+       memset(&addr_i, 0, sizeof(addr_i));
+       debug_print("socket->type: %d\n", Socket->type);
+       switch (Socket->type) {
+               case UNIX_SOCKET:
+                       debug_print("socket path: %s\n", Socket->socket.path);
+                       sock = socket(PF_UNIX, SOCK_STREAM, 0);
+                       debug_print("socket file (create): %d\n", sock);
+                       if (sock < 0) 
+                               return;
+                       addr_u.sun_family = AF_UNIX;
+                       memcpy(addr_u.sun_path, Socket->socket.path, 
+                                       strlen(Socket->socket.path));
+                       if (connect(sock, (struct sockaddr *) &addr_u, sizeof(addr_u)) < 0) {
+                               perror("connect socket");
+                               close_socket();
+                               sock = -2;
+                       }
+                       debug_print("socket file (connect): %d\n", sock);
+                       break;
+               case INET_SOCKET:
+                       addr_i.sin_family = AF_INET;
+                       addr_i.sin_port = htons(Socket->socket.port);
+                       hp = gethostbyname(Socket->socket.host);
+                       bcopy((void *)hp->h_addr, (void *)&addr_i.sin_addr, hp->h_length);
+                       sock = socket(PF_INET, SOCK_STREAM, 0);
+                       if (sock < 0)
+                               return;
+                       if (connect(sock, (struct sockaddr *)&addr_i, sizeof(addr_i)) < 0) {
+                               perror("connect socket");
+                               close_socket();
+                               sock = -2;
+                       }
+                       break;
+       }
+       /*debug_set_mode(FALSE);*/
+}
+
+static void copy_socket(Clamd_Socket* sock) {
+       Socket = (Clamd_Socket *) malloc(sizeof(Clamd_Socket *));
+       Socket->socket.path = NULL;
+       Socket->socket.host = NULL;
+       Socket->type = sock->type;
+       if (Socket->type == UNIX_SOCKET) {
+               Socket->socket.path = g_strdup(sock->socket.path);
+       }
+       else {
+               Socket->socket.host = g_strdup(sock->socket.host);
+               Socket->socket.port = sock->socket.port;
+       }
+}
+
+Clamd_Stat clamd_init(Clamd_Socket* config) {
+       gchar buf[BUFSIZ];
+       int n_read;
+       gboolean connect = FALSE;
+
+       /*debug_set_mode(TRUE);*/
+       if (config != NULL && Socket != NULL)
+               return NO_SOCKET;
+       if (config) {
+               debug_print("socket: %s\n", config->socket.path);
+               copy_socket(config);
+       }
+       create_socket();
+       if (sock < 0) {
+               debug_print("no connection\n");
+               return NO_CONNECTION;
+       }
+       if (write(sock, ping, strlen(ping)) == -1) {
+               debug_print("no connection\n");
+               return NO_CONNECTION;
+       }
+       memset(buf, '\0', sizeof(buf));
+       while ((n_read = read(sock, buf, BUFSIZ)) > 0) {
+               if (buf[strlen(buf) - 1] == '\n')
+                       buf[strlen(buf) - 1] = '\0';
+               debug_print("Ping result: %s\n", buf);
+               if (strcmp("PONG", buf) == 0)
+                       connect = TRUE;
+       }
+       close_socket();
+       create_socket();
+       if (sock < 0) {
+           debug_print("no connection\n");
+           return NO_CONNECTION;
+       }
+       if (write(sock, version, strlen(version)) == -1) {
+           debug_print("no connection\n");
+           return NO_CONNECTION;
+       }
+       memset(buf, '\0', sizeof(buf));
+        while ((n_read = read(sock, buf, BUFSIZ)) > 0) {
+           if (buf[strlen(buf) - 1] == '\n')
+               buf[strlen(buf) - 1] = '\0';
+           debug_print("Version: %s\n", buf);
+       }
+       close_socket();
+       /*debug_set_mode(FALSE);*/
+       return (connect) ? OK : NO_CONNECTION;
+}
+
+static Clamd_Stat clamd_stream_scan(
+               const gchar* path, gchar** res, ssize_t size) {
+       int fd;
+       ssize_t count;
+       gchar buf[BUFSIZ];
+       int n_read;
+       int32_t chunk;
+       
+       debug_print("Scanning: %s\n", path);
+
+       memset(buf, '\0', sizeof(buf));
+
+       if (! res || size < 1) {
+               return SCAN_ERROR;
+       }
+       if (! *res)
+               *res = g_new(gchar, size);
+       memset(*res, '\0', size);
+       
+       if (! g_file_test(path, G_FILE_TEST_EXISTS)) {
+               *res = g_strconcat("ERROR -> ", path, _(": File does not exist"), NULL);
+               debug_print("res: %s\n", *res);
+               return SCAN_ERROR;
+       }
+
+#ifdef _LARGE_FILES
+       fd = open(path, O_RDONLY, O_LARGEFILE);
+#else
+       fd = open(path, O_RDONLY);
+#endif
+
+       if (fd < 0) {
+               /*g_error("%s: Unable to open", path);*/
+               *res = g_strconcat("ERROR -> ", path, _(": Unable to open"), NULL);
+               return SCAN_ERROR;
+       }
+       
+       debug_print("command: %s\n", instream);
+       if (write(sock, instream, strlen(instream) + 1) == -1) {
+               close(fd);
+               return NO_CONNECTION;
+       }
+
+       while ((count = read(fd, (void *) buf, sizeof(buf))) > 0) {
+               if (count == -1) {
+                       close(fd);
+                       *res = g_strconcat("ERROR -> ", path, _("%s: Error reading"), NULL);
+                       return SCAN_ERROR;
+               }
+               if (buf[strlen(buf) - 1] == '\n')
+                       buf[strlen(buf) - 1] = '\0';
+               debug_print("read: %ld bytes\n", count);
+               
+               debug_print("chunk size: %ld\n", count);
+               chunk = htonl(count);
+               if (write(sock, &chunk, 4) == -1) {
+                       close(fd);
+                       *res = g_strconcat("ERROR -> ", _("Socket write error"), NULL);
+                       return SCAN_ERROR;
+               }
+               if (write(sock, buf, count) == -1) {
+                       close(fd);
+                       *res = g_strconcat("ERROR -> ", _("Socket write error"), NULL);
+                       return SCAN_ERROR;
+               }
+               memset(buf, '\0', sizeof(buf));
+       }
+       close(fd);
+       
+       chunk = htonl(0);
+       if (write(sock, &chunk, 4) == -1) {
+               *res = g_strconcat("ERROR -> ", _("Socket write error"), NULL);
+               return SCAN_ERROR;
+       }
+       
+       debug_print("reading from socket\n");
+       n_read = read(sock, *res, size);
+       if (n_read < 0) {
+               *res = g_strconcat("ERROR -> ", _("Socket read error"), NULL);
+               return SCAN_ERROR;
+       }
+       debug_print("received: %s\n", *res);
+       return OK;
+}
+
+Clamd_Stat clamd_verify_email(const gchar* path, response* result) {
+       gchar buf[BUFSIZ];
+       int n_read;
+       gchar* command;
+       Clamd_Stat stat;
+
+       /*debug_set_mode(TRUE);*/
+       if (!result) {
+               result = malloc(sizeof(response *));
+               memset(result, '\0', sizeof(response *));
+       }
+       create_socket();
+       if (sock < 0) {
+               debug_print("no connection\n");
+               return NO_CONNECTION;
+       }
+       memset(buf, '\0', sizeof(buf));
+       if (Socket->type == INET_SOCKET) {
+               gchar* tmp = g_new0(gchar, BUFSIZ);
+               stat = clamd_stream_scan(path, &tmp, BUFSIZ);
+               if (stat != OK) {
+                       close_socket();
+                       result->msg = g_strdup(tmp);
+                       g_free(tmp);
+                       debug_print("result: %s\n", result->msg);
+                       /*debug_set_mode(FALSE);*/
+                       return stat;
+               }
+               debug_print("copy to buf: %s\n", tmp);
+               memcpy(&buf, tmp, BUFSIZ);
+               g_free(tmp);
+       }
+       else {
+               command = g_strconcat(scan, " ", path, "\n", NULL);
+               debug_print("command: %s\n", command);
+               if (write(sock, command, strlen(command)) == -1) {
+                       debug_print("no connection\n");
+                       stat = NO_CONNECTION;
+               }
+               g_free(command);
+               memset(buf, '\0', sizeof(buf));
+               while ((n_read = read(sock, buf, BUFSIZ)) > 0) {
+                       if (buf[strlen(buf) - 1] == '\n')
+                               buf[strlen(buf) - 1] = '\0';
+               }
+       }
+       debug_print("response: %s\n", buf);
+       if (strstr(buf, "ERROR")) {
+               stat = SCAN_ERROR;
+               result->msg = g_strdup(buf);
+       }               
+       else if (strstr(buf, "FOUND")) {
+               stat = VIRUS;
+               result->msg = g_strdup(buf);
+       }               
+       else {
+               stat = OK;
+               result->msg = NULL;
+       }
+       close_socket();
+       /*debug_set_mode(FALSE);*/
+
+       return stat;
+}
+
+GSList* clamd_verify_dir(const gchar* path) {
+       gchar buf[BUFSIZ];
+       int n_read;
+       gchar* command;
+       GSList *list = NULL;
+
+       if (Socket->type == INET_SOCKET)
+               return list;
+
+       create_socket();
+       if (sock < 0) {
+               debug_print("No socket\n");
+               return list;
+       }
+       command = g_strconcat(contscan, path, "\n", NULL);
+       debug_print("command: %s\n", command);
+       if (write(sock, command, strlen(command)) == -1) {
+               debug_print("No socket\n");
+               return list;
+       }
+       g_free(command);
+       memset(buf, '\0', sizeof(buf));
+       while ((n_read = read(sock, buf, BUFSIZ)) > 0) {
+               gchar** tmp = g_strsplit(buf, "\n", 0);
+               gchar** head = tmp;
+               while (*tmp) {
+                       gchar* file = *tmp++;
+                       debug_print("%s\n", file);
+                       if (strstr(file, "ERROR")) {
+                               g_warning("%s", file);
+                               /* dont report files with errors */
+                       }
+                       else if (strstr(file, "FOUND")) {
+                               list = g_slist_append(list, g_strdup(file));
+                       }
+               }
+               g_strfreev(head);
+       }
+       close_socket();
+       return list;
+}
+
+void clamd_free_gslist(GSList* list) {
+       GSList* tmp = list;
+       while(tmp) {
+               g_free(tmp->data);
+               tmp = g_slist_next(tmp);
+       }
+       g_slist_free(list);
+}
+
+gchar* clamd_get_virus_name(gchar* msg) {
+       gchar *head, *tail, *name;
+
+       tail = g_strrstr_len(msg, strlen(msg), "FOUND");
+       if (! tail)
+               return NULL;
+       head = g_strstr_len(msg, strlen(msg), ":");
+       ++head;
+       name = g_strndup(head, tail - head);
+       g_strstrip(name);
+       return name;
+}
+
+void clamd_free() {
+/*
+ * struct _Clamd_Socket {
+ *     Type type;
+ *     union {
+ *             struct {
+ *                 gchar*  path;
+ *             };
+ *             struct {
+ *                 gchar*  host;
+ *                 int     port;
+ *             };
+ *     } socket;
+ *  };
+ */
+       if (sock > 0) {
+               close_socket();
+               sock = 0;
+       }
+       if (Socket) {
+               switch (Socket->type) {
+                   case UNIX_SOCKET:
+                       if (Socket->socket.path) {
+                           g_free(Socket->socket.path);
+                           Socket->socket.path = NULL;
+                       }
+                       break;
+                   case INET_SOCKET:
+                       if (Socket->socket.host) {
+                           g_free(Socket->socket.host);
+                           Socket->socket.host = NULL;
+                       }
+                       break;
+               }
+               g_free(Socket);
+               Socket = NULL;
+       }
+       if (config) {
+           clamd_config_free(config);
+           config = NULL;
+       }
+}
+
+Config* clamd_config_new() {
+       return g_new0(Config, 1);
+}
+
+void clamd_config_free(Config* c) {
+       if (c->ConfigType == AUTOMATIC) {
+               g_free(c->automatic.folder);
+               c->automatic.folder = NULL;
+       }
+       else {
+               g_free(c->manual.host);
+               c->manual.host = NULL;
+       }
+       g_free(c);
+}
+
+gchar* int2char(int i) {
+       gchar* s = g_new0(gchar, 5);
+
+       sprintf(s, "%d", i);
+       
+       return s;
+}
+
+gchar* long2char(long l) {
+       gchar* s = g_new0(gchar, 5);
+
+       debug_print("l: %ld\n", l);
+       sprintf(s, "%ld", l);
+       debug_print("s: %s\n", s);
+       
+       return s;
+}
diff --git a/src/plugins/clamd/libclamd/clamd-plugin.h b/src/plugins/clamd/libclamd/clamd-plugin.h
new file mode 100644 (file)
index 0000000..1dfc004
--- /dev/null
@@ -0,0 +1,144 @@
+/* vim: set textwidth=80 tabstop=4: */
+
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2008 Michael Rasmussen and the Claws Mail Team
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+#ifndef __CLAMD_PLUGIN_H__
+#define __CLAMD_PLUGIN_H__
+
+#include <glib.h>
+
+typedef enum _Type Type;
+enum _Type { UNIX_SOCKET, INET_SOCKET };
+
+typedef enum _Clamd_Stat Clamd_Stat;
+enum _Clamd_Stat { OK, VIRUS, NO_SOCKET, NO_CONNECTION, SCAN_ERROR };
+
+typedef struct _Clamd_Socket Clamd_Socket;
+struct _Clamd_Socket {
+       Type type;
+       union {
+               struct {
+                       gchar*  path;
+               };
+               struct {
+                       gchar*  host;
+                       int             port;
+               };
+       } socket;
+};
+
+typedef struct {
+       enum { AUTOMATIC, MANUAL } ConfigType;
+       union {
+               struct {
+                       gchar*  folder;
+               } automatic;
+               struct {
+                       gchar*  host;
+                       int             port;
+               } manual;
+       };
+} Config;
+       
+typedef struct _response response;
+struct _response {
+       gchar* msg;
+};
+
+void clamd_create_config_automatic(const gchar* path);
+
+void clamd_create_config_manual(const gchar* host, int port);
+
+gchar* int2char(int i);
+
+gchar* long2char(long l);
+
+/**
+ * Function which looks for clamd.conf the default places
+ * and configures the plugin according to the information
+ * found.
+ * @return <b>TRUE</b> if clamd.conf found which means all
+ * information need to make a connection has been found.
+ * <b>FALSE</b> otherwise.
+ */
+gboolean clamd_find_socket();
+
+/**
+ * Function to get current configuration
+ * @return the current configuration for clamd or <b>NULL</b>
+ */
+Config* clamd_get_config();
+
+/**
+ * Function to retrieve virus name from msg
+ * @param msg Message returned from clamd
+ * @return virus name or <b>NULL</b> if no virus name found
+ */
+gchar* clamd_get_virus_name(gchar* msg);
+
+/**
+ * Function to initialize the connection to clamd.
+ * @param config A pointer to a struct _Clamd_Socket having
+ * the required information. If clamd_find_socket returned
+ * TRUE config should be <b>NULL</b> because all the needed
+ * information is already present @see clamd_find_socket.
+ * @return Clamd_Stat. @see _Clamd_Stat.
+ */
+Clamd_Stat clamd_init(Clamd_Socket* config);
+
+/**
+ * Function returning the current socket information.
+ * @return reference to the current Clamd_Socket. @see _Clamd_Socket.
+ */
+Clamd_Socket* clamd_get_socket();
+
+/**
+ * Function which is checks a specific email for known viruses
+ * @param path Absolut path to email to check.
+ * @param msg String to which result of scan will be copied. Will be
+ * <b>NULL</b> if no virus was found.
+ * @return Clamd_Stat. @see _Clamd_Stat.
+ */
+Clamd_Stat clamd_verify_email(const gchar* path, response* result);
+
+/**
+ * Function which is checks files in a specific directory for
+ * known viruses. Dont stop when a virus is found but keeps going
+ * @param path Absolut path to directory to check.
+ * @return list of list with virus or <b>NULL</b>.
+ */
+GSList* clamd_verify_dir(const gchar* path);
+
+/**
+ * Function to free all memory assigned to a GSList
+ * @param list The GSList to free
+ */
+void clamd_free_gslist(GSList* list);
+
+/**
+ * Function which frees all memory assigned to clamd_plugin
+ */
+void clamd_free();
+
+Config* clamd_config_new();
+
+void clamd_config_free(Config* c);
+
+#endif
diff --git a/src/plugins/clamd/placeholder.txt b/src/plugins/clamd/placeholder.txt
deleted file mode 100644 (file)
index 3b94f91..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Placeholder
diff --git a/src/plugins/fetchinfo/Makefile.am b/src/plugins/fetchinfo/Makefile.am
new file mode 100644 (file)
index 0000000..fde86c2
--- /dev/null
@@ -0,0 +1,23 @@
+plugindir = $(pkglibdir)/plugins
+
+plugin_LTLIBRARIES = fetchinfo_plugin.la
+
+fetchinfo_plugin_la_SOURCES = \
+       fetchinfo_plugin.c fetchinfo_plugin.h \
+       fetchinfo_plugin_gtk.c
+
+fetchinfo_plugin_la_LDFLAGS = \
+       -avoid-version -module \
+       $(GTK_LIBS)
+
+AM_CPPFLAGS = \
+       $(CLAWS_MAIL_CFLAGS) \
+       $(GLIB_CFLAGS) \
+       $(GTK_CFLAGS)
+
+INCLUDES = \
+       -I$(top_srcdir)/src \
+       -I$(top_srcdir)/src/common \
+       -I$(top_srcdir)/src/gtk
+
+EXTRA_DIST=
diff --git a/src/plugins/fetchinfo/fetchinfo_plugin.c b/src/plugins/fetchinfo/fetchinfo_plugin.c
new file mode 100644 (file)
index 0000000..d3ef5ec
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2003 Hiroyuki Yamamoto and the Claws Mail Team
+ *
+ * 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 3 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#  include "claws-features.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+/* common */
+#include "version.h"
+#include "claws.h"
+#include "plugin.h"
+#include "utils.h"
+#include "hooks.h"
+#include "inc.h"
+#include "prefs.h"
+#include "prefs_gtk.h"
+#include "fetchinfo_plugin.h"
+/* add headers */
+#include "pop.h"
+#include "quoted-printable.h"
+/* parse headers */
+#include "procheader.h"
+#include "plugin.h"
+
+static guint mail_receive_hook_id;
+
+static FetchinfoConfig config;
+
+static PrefParam param[] = {
+       {"fetchinfo_enable",    "FALSE", &config.fetchinfo_enable,              
+                               P_BOOL, NULL, NULL, NULL},
+       {"fetchinfo_uidl",      "TRUE", &config.fetchinfo_uidl,         
+                               P_BOOL, NULL, NULL, NULL},
+       {"fetchinfo_account",   "TRUE", &config.fetchinfo_account,
+                               P_BOOL, NULL, NULL, NULL},
+       {"fetchinfo_server",    "TRUE", &config.fetchinfo_server,
+                               P_BOOL, NULL, NULL, NULL},
+       {"fetchinfo_userid",    "TRUE", &config.fetchinfo_userid,
+                               P_BOOL, NULL, NULL, NULL},
+       {"fetchinfo_time",      "TRUE", &config.fetchinfo_time,
+                               P_BOOL, NULL, NULL, NULL},
+
+       {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
+};
+
+gchar *fetchinfo_add_header(gchar **data, const gchar *header,const gchar *value)
+{
+       gchar *line;
+       gchar *qpline;
+       gchar *newdata;
+
+       line = g_strdup_printf("%s: %s", header, value);
+       qpline = g_malloc(strlen(line)*4);
+       qp_encode_line(qpline, line);
+       newdata = g_strconcat(*data, qpline, NULL);
+       g_free(line);
+       g_free(qpline);
+       g_free(*data);
+       *data = newdata;
+       return newdata;
+}
+
+static gboolean mail_receive_hook(gpointer source, gpointer data)
+{
+       MailReceiveData *mail_receive_data = (MailReceiveData *) source;
+       Pop3Session *session;
+       gchar *newheaders;
+       gchar *newdata;
+       gchar date[PREFSBUFSIZE];
+       
+       if (!config.fetchinfo_enable) {
+               return FALSE;
+       }
+
+       g_return_val_if_fail( 
+                             mail_receive_data
+                             && mail_receive_data->session
+                             && mail_receive_data->data,
+                             FALSE );
+
+       session = mail_receive_data->session;
+       get_rfc822_date(date, PREFSBUFSIZE);
+       newheaders = g_strdup("");
+
+       if (config.fetchinfo_uidl)
+               fetchinfo_add_header(&newheaders, "X-FETCH-UIDL", 
+                       session->msg[session->cur_msg].uidl);
+       if (config.fetchinfo_account)
+               fetchinfo_add_header(&newheaders, "X-FETCH-ACCOUNT", 
+                       session->ac_prefs->account_name);
+       if (config.fetchinfo_server)
+               fetchinfo_add_header(&newheaders, "X-FETCH-SERVER", 
+                       session->ac_prefs->recv_server);
+       if (config.fetchinfo_userid)
+               fetchinfo_add_header(&newheaders, "X-FETCH-USERID", 
+                       session->ac_prefs->userid);
+       if (config.fetchinfo_time)
+               fetchinfo_add_header(&newheaders, "X-FETCH-TIME", 
+                       date);
+
+       newdata = g_strconcat(newheaders, mail_receive_data->data, NULL);
+       g_free(newheaders);
+       g_free(mail_receive_data->data);
+       mail_receive_data->data = newdata;
+       mail_receive_data->data_len = strlen(newdata);
+       return FALSE;
+}
+
+FetchinfoConfig *fetchinfo_get_config(void)
+{
+       return &config;
+}
+
+void fetchinfo_save_config(void)
+{
+       PrefFile *pfile;
+       gchar *rcpath;
+
+       debug_print("Saving Fetchinfo Page\n");
+
+       rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+       pfile = prefs_write_open(rcpath);
+       g_free(rcpath);
+       if (!pfile || (prefs_set_block_label(pfile, "Fetchinfo") < 0))
+               return;
+
+       if (prefs_write_param(param, pfile->fp) < 0) {
+               /* i18n: Possible error message during plugin load */
+               g_warning(_("failed to write Fetchinfo configuration to file\n"));
+               prefs_file_close_revert(pfile);
+               return;
+       }
+        if (fprintf(pfile->fp, "\n") < 0) {
+               FILE_OP_ERROR(rcpath, "fprintf");
+               prefs_file_close_revert(pfile);
+       } else
+               prefs_file_close(pfile);
+}
+
+gint plugin_init(gchar **error)
+{
+       gchar *rcpath;
+
+       if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
+                               VERSION_NUMERIC, _("Fetchinfo"), error))
+               return -1;
+
+       mail_receive_hook_id = hooks_register_hook(MAIL_RECEIVE_HOOKLIST, mail_receive_hook, NULL);
+       if (mail_receive_hook_id == (guint)-1) {
+               /* i18n: Possible error message during plugin load */
+               *error = g_strdup(_("Failed to register mail receive hook"));
+               return -1;
+       }
+
+       prefs_set_default(param);
+       rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+       prefs_read_config(param, "Fetchinfo", rcpath, NULL);
+       g_free(rcpath);
+
+       fetchinfo_gtk_init();
+
+       debug_print("Fetchinfo plugin loaded\n");
+
+       return 0;
+}
+
+gboolean plugin_done(void)
+{
+       hooks_unregister_hook(MAIL_RECEIVE_HOOKLIST, mail_receive_hook_id);
+       fetchinfo_gtk_done();
+
+       debug_print("Fetchinfo plugin unloaded\n");
+       return TRUE;
+}
+
+const gchar *plugin_name(void)
+{
+       return _("Fetchinfo");
+}
+
+const gchar *plugin_desc(void)
+{
+       /* i18n: Description seen in plugins dialog.
+        * Translation of "Plugins" part of preferences path should to be
+        * the same as translation of "Plugins" string in Claws Mail message
+        * catalog. */
+       return _("This plugin modifies the downloaded messages. "
+                "It inserts headers containing some download "
+                "information: UIDL, Claws Mail account name, "
+                "POP server, user ID and retrieval time.\n"
+            "\n"
+            "Options can be found in /Configuration/Preferences/Plugins/Fetchinfo");
+}
+
+const gchar *plugin_type(void)
+{
+       return "GTK2";
+}
+
+const gchar *plugin_licence(void)
+{
+               return "GPL3+";
+}
+
+const gchar *plugin_version(void)
+{
+       return VERSION;
+}
+
+struct PluginFeature *plugin_provides(void)
+{
+       static struct PluginFeature features[] = 
+               /* i18n: Description of functionality added by this plugin */
+               { {PLUGIN_UTILITY, N_("Mail marking")},
+                 {PLUGIN_NOTHING, NULL}};
+       return features;
+}
diff --git a/src/plugins/fetchinfo/fetchinfo_plugin.h b/src/plugins/fetchinfo/fetchinfo_plugin.h
new file mode 100644 (file)
index 0000000..c5ad7b8
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2003 Hiroyuki Yamamoto and the Claws Mail Team
+ *
+ * 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 3 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 FETCHINFO_PLUGIN_H
+#define FETCHINFO_PLUGIN_H 1
+
+#include <glib.h>
+
+typedef struct _FetchinfoConfig FetchinfoConfig;
+
+struct _FetchinfoConfig
+{
+       gboolean         fetchinfo_enable;
+       gboolean         fetchinfo_uidl;
+       gboolean         fetchinfo_account;
+       gboolean         fetchinfo_server;
+       gboolean         fetchinfo_userid;
+       gboolean         fetchinfo_time;
+};
+
+FetchinfoConfig *fetchinfo_get_config          (void);
+void         fetchinfo_save_config     (void);
+gint fetchinfo_gtk_init(void);
+void fetchinfo_gtk_done(void);
+
+#endif
diff --git a/src/plugins/fetchinfo/fetchinfo_plugin_gtk.c b/src/plugins/fetchinfo/fetchinfo_plugin_gtk.c
new file mode 100644 (file)
index 0000000..80f4a6d
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2003 Hiroyuki Yamamoto and the Claws Mail Team
+ *
+ * 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 3 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#  include "claws-features.h"
+#endif
+
+#include "defs.h"
+#include "version.h"
+#include "claws.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "plugin.h"
+#include "utils.h"
+#include "prefs.h"
+#include "prefs_gtk.h"
+
+#include "fetchinfo_plugin.h"
+
+struct FetchinfoPage
+{
+       PrefsPage page;
+       
+       GtkWidget *fetchinfo_enable;
+       GtkWidget *fetchinfo_uidl;
+       GtkWidget *fetchinfo_account;
+       GtkWidget *fetchinfo_server;
+       GtkWidget *fetchinfo_userid;
+       GtkWidget *fetchinfo_time;
+};
+
+static void fetchinfo_set_sensitive(struct FetchinfoPage *page, gboolean enable)
+{
+       gtk_widget_set_sensitive(GTK_WIDGET(page->fetchinfo_uidl), enable);
+       gtk_widget_set_sensitive(GTK_WIDGET(page->fetchinfo_account), enable);
+       gtk_widget_set_sensitive(GTK_WIDGET(page->fetchinfo_server), enable);
+       gtk_widget_set_sensitive(GTK_WIDGET(page->fetchinfo_userid), enable);
+       gtk_widget_set_sensitive(GTK_WIDGET(page->fetchinfo_time), enable);
+}
+
+static void fetchinfo_enable_cb(GtkWidget *widget, gpointer data)
+{
+       struct FetchinfoPage *page = (struct FetchinfoPage *) data;
+
+       fetchinfo_set_sensitive(page, gtk_toggle_button_get_active(
+               GTK_TOGGLE_BUTTON(page->fetchinfo_enable)));
+}
+
+#define ADD_NEW_CHECKBOX(line, button, text) \
+       button = gtk_check_button_new_with_label (text); \
+       gtk_widget_show (button); \
+       gtk_table_attach (GTK_TABLE (table), button, 1, 2, line, line+1, \
+                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), \
+                                 (GtkAttachOptions) (0), 0, 0);
+
+static void fetchinfo_create_widget_func(PrefsPage * _page, GtkWindow *window, gpointer data)
+{
+       struct FetchinfoPage *page = (struct FetchinfoPage *) _page;
+       FetchinfoConfig *config;
+       GtkWidget *table;
+       GtkWidget *fetchinfo_enable;
+       GtkWidget *fetchinfo_uidl;
+       GtkWidget *fetchinfo_account;
+       GtkWidget *fetchinfo_server;
+       GtkWidget *fetchinfo_userid;
+       GtkWidget *fetchinfo_time;
+
+       table = gtk_table_new (6, 3, FALSE);
+       gtk_widget_show(table);
+       gtk_table_set_row_spacings(GTK_TABLE(table), 4);
+       gtk_table_set_col_spacings(GTK_TABLE(table), 8);
+
+       /* i18n: Heading of a preferences section determining which headers to add */
+       fetchinfo_enable = gtk_check_button_new_with_label (_("Add fetchinfo headers"));
+       gtk_widget_show (fetchinfo_enable);
+       gtk_table_attach (GTK_TABLE (table), fetchinfo_enable, 0, 2, 0, 1,
+                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+                                 (GtkAttachOptions) (0), 0, 0);
+
+       /* i18n: Description of a header to be added */
+       ADD_NEW_CHECKBOX(1, fetchinfo_uidl,     _("UIDL"));
+       /* i18n: Description of a header to be added */
+       ADD_NEW_CHECKBOX(2, fetchinfo_account,  _("Account name"));
+       /* i18n: Description of a header to be added */
+       ADD_NEW_CHECKBOX(3, fetchinfo_server,   _("Receive server"));
+       /* i18n: Description of a header to be added */
+       ADD_NEW_CHECKBOX(4, fetchinfo_userid,   _("UserID"));
+       /* i18n: Description of a header to be added */
+       ADD_NEW_CHECKBOX(5, fetchinfo_time,     _("Fetch time"));
+
+       config = fetchinfo_get_config();
+
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fetchinfo_enable),
+                                    config->fetchinfo_enable);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fetchinfo_uidl),
+                                    config->fetchinfo_uidl);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fetchinfo_account),
+                                    config->fetchinfo_account);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fetchinfo_server),
+                                    config->fetchinfo_server);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fetchinfo_userid),
+                                    config->fetchinfo_userid);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fetchinfo_time),
+                                    config->fetchinfo_time);
+
+       g_signal_connect(G_OBJECT(fetchinfo_enable), "released",
+                        G_CALLBACK(fetchinfo_enable_cb), page);
+
+       page->fetchinfo_enable  = fetchinfo_enable;
+       page->fetchinfo_uidl    = fetchinfo_uidl;
+       page->fetchinfo_account = fetchinfo_account;
+       page->fetchinfo_server  = fetchinfo_server;
+       page->fetchinfo_userid  = fetchinfo_userid;
+       page->fetchinfo_time    = fetchinfo_time;
+
+       page->page.widget = table;
+
+       fetchinfo_set_sensitive(page, config->fetchinfo_enable);
+}
+#undef ADD_NEW_CHECKBOX
+
+static void fetchinfo_destroy_widget_func(PrefsPage *_page)
+{
+       debug_print("Destroying Fetchinfo widget\n");
+}
+
+static void fetchinfo_save_func(PrefsPage *_page)
+{
+       struct FetchinfoPage *page = (struct FetchinfoPage *) _page;
+       FetchinfoConfig *config;
+
+       debug_print("Saving Fetchinfo Page\n");
+
+       config = fetchinfo_get_config();
+
+       config->fetchinfo_enable  = gtk_toggle_button_get_active(
+               GTK_TOGGLE_BUTTON(page->fetchinfo_enable) );
+       config->fetchinfo_uidl    = gtk_toggle_button_get_active(
+               GTK_TOGGLE_BUTTON(page->fetchinfo_uidl)   );
+       config->fetchinfo_account = gtk_toggle_button_get_active(
+               GTK_TOGGLE_BUTTON(page->fetchinfo_account));
+       config->fetchinfo_server  = gtk_toggle_button_get_active(
+               GTK_TOGGLE_BUTTON(page->fetchinfo_server) );
+       config->fetchinfo_userid  = gtk_toggle_button_get_active(
+               GTK_TOGGLE_BUTTON(page->fetchinfo_userid) );
+       config->fetchinfo_time    = gtk_toggle_button_get_active(
+               GTK_TOGGLE_BUTTON(page->fetchinfo_time)   );
+
+       fetchinfo_save_config();
+}
+
+static struct FetchinfoPage fetchinfo_page;
+
+gint fetchinfo_gtk_init(void)
+{
+       static gchar *path[3];
+
+       path[0] = _("Plugins");
+       path[1] = _("Fetchinfo");
+       path[2] = NULL;
+
+       fetchinfo_page.page.path = path;
+       fetchinfo_page.page.create_widget = fetchinfo_create_widget_func;
+       fetchinfo_page.page.destroy_widget = fetchinfo_destroy_widget_func;
+       fetchinfo_page.page.save_page = fetchinfo_save_func;
+       
+       prefs_gtk_register_page((PrefsPage *) &fetchinfo_page);
+
+       debug_print("Fetchinfo GTK plugin loaded\n");
+       return 0;       
+}
+
+void fetchinfo_gtk_done(void)
+{
+       prefs_gtk_unregister_page((PrefsPage *) &fetchinfo_page);
+
+       debug_print("Fetchinfo GTK plugin unloaded\n");
+}
diff --git a/src/plugins/fetchinfo/placeholder.txt b/src/plugins/fetchinfo/placeholder.txt
deleted file mode 100644 (file)
index 3b94f91..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Placeholder
diff --git a/src/plugins/gdata/Makefile.am b/src/plugins/gdata/Makefile.am
new file mode 100644 (file)
index 0000000..abc62ee
--- /dev/null
@@ -0,0 +1,30 @@
+plugindir = $(pkglibdir)/plugins
+
+plugin_LTLIBRARIES = gdata_plugin.la
+
+gdata_plugin_la_SOURCES = \
+       gdata_plugin.c \
+       gdata_plugin.h \
+       cm_gdata_contacts.c \
+       cm_gdata_contacts.h \
+       cm_gdata_prefs.c \
+       cm_gdata_prefs.h
+
+gdata_plugin_la_LDFLAGS = \
+       -avoid-version -module \
+       $(GTK_LIBS) \
+       $(GDATA_LIBS)
+
+
+INCLUDES = \
+       -I$(top_srcdir)/src \
+       -I$(top_srcdir)/src/common \
+       -I$(top_srcdir)/src/gtk
+
+AM_CPPFLAGS = \
+       -Wall \
+       $(CLAWS_MAIL_CFLAGS) \
+       $(GLIB_CFLAGS) \
+       $(GTK_CFLAGS) \
+       $(GDATA_CFLAGS) \
+       -DLOCALEDIR=\""$(localedir)"\"
diff --git a/src/plugins/gdata/cm_gdata_contacts.c b/src/plugins/gdata/cm_gdata_contacts.c
new file mode 100644 (file)
index 0000000..fc48918
--- /dev/null
@@ -0,0 +1,508 @@
+/* GData plugin for Claws-Mail
+ * Copyright (C) 2011 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#  include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "cm_gdata_contacts.h"
+#include "cm_gdata_prefs.h"
+
+#include <gtk/gtk.h>
+#include "addr_compl.h"
+#include "main.h"
+#include "prefs_common.h"
+#include "common/log.h"
+#include "common/xml.h"
+
+#include <gdata/gdata.h>
+
+#define GDATA_CONTACTS_FILENAME "gdata_cache.xml"
+
+typedef struct
+{
+  const gchar *family_name;
+  const gchar *given_name;
+  const gchar *full_name;
+  const gchar *address;
+} Contact;
+
+typedef struct
+{
+  GSList *contacts;
+} CmGDataContactsCache;
+
+
+CmGDataContactsCache contacts_cache;
+gboolean cm_gdata_contacts_query_running = FALSE;
+gchar *contacts_group_id = NULL;
+
+static void write_cache_to_file(void)
+{
+  gchar *path;
+  PrefFile *pfile;
+  XMLTag *tag;
+  XMLNode *xmlnode;
+  GNode *rootnode;
+  GNode *contactsnode;
+  GSList *walk;
+
+  path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, GDATA_CONTACTS_FILENAME, NULL);
+  pfile = prefs_write_open(path);
+  g_free(path);
+  if(pfile == NULL) {
+    debug_print("GData plugin error: Cannot open file " GDATA_CONTACTS_FILENAME " for writing\n");
+    return;
+  }
+
+  /* XML declarations */
+  xml_file_put_xml_decl(pfile->fp);
+
+  /* Build up XML tree */
+
+  /* root node */
+  tag = xml_tag_new("gdata");
+  xmlnode = xml_node_new(tag, NULL);
+  rootnode = g_node_new(xmlnode);
+
+  /* contacts node */
+  tag = xml_tag_new("contacts");
+  xmlnode = xml_node_new(tag, NULL);
+  contactsnode = g_node_new(xmlnode);
+  g_node_append(rootnode, contactsnode);
+
+  /* walk contacts cache */
+  for(walk = contacts_cache.contacts; walk; walk = walk->next)
+  {
+    GNode *contactnode;
+    Contact *contact = walk->data;
+    tag = xml_tag_new("contact");
+    xml_tag_add_attr(tag, xml_attr_new("family_name",contact->family_name));
+    xml_tag_add_attr(tag, xml_attr_new("given_name",contact->given_name));
+    xml_tag_add_attr(tag, xml_attr_new("full_name",contact->full_name));
+    xml_tag_add_attr(tag, xml_attr_new("address",contact->address));
+    xmlnode = xml_node_new(tag, NULL);
+    contactnode = g_node_new(xmlnode);
+    g_node_append(contactsnode, contactnode);
+  }
+
+  /* Actual writing and cleanup */
+  xml_write_tree(rootnode, pfile->fp);
+  if(prefs_file_close(pfile) < 0)
+    debug_print("GData plugin error: Failed to write file " GDATA_CONTACTS_FILENAME "\n");
+
+  debug_print("GData plugin error: Wrote cache to file " GDATA_CONTACTS_FILENAME "\n");
+
+  /* Free XML tree */
+  xml_free_tree(rootnode);
+}
+
+static int add_gdata_contact_to_cache(GDataContactsContact *contact)
+{
+  GList *walk;
+  int retval;
+
+  retval = 0;
+  for(walk = gdata_contacts_contact_get_email_addresses(contact); walk; walk = walk->next) {
+    const gchar *email_address;
+    GDataGDEmailAddress *address = GDATA_GD_EMAIL_ADDRESS(walk->data);
+
+    email_address = gdata_gd_email_address_get_address(address);
+    if(email_address && (*email_address != '\0')) {
+      GDataGDName *name;
+      Contact *cached_contact;
+
+      name = gdata_contacts_contact_get_name(contact);
+
+      cached_contact = g_new0(Contact, 1);
+      cached_contact->full_name = g_strdup(gdata_gd_name_get_full_name(name));
+      cached_contact->given_name = g_strdup(gdata_gd_name_get_given_name(name));
+      cached_contact->family_name = g_strdup(gdata_gd_name_get_family_name(name));
+      cached_contact->address = g_strdup(email_address);
+
+      contacts_cache.contacts = g_slist_prepend(contacts_cache.contacts, cached_contact);
+
+      debug_print("GData plugin: Added %s <%s>\n", cached_contact->full_name, cached_contact->address);
+      retval = 1;
+    }
+  }
+  if(retval == 0)
+  {
+    debug_print("GData plugin: Skipped received contact \"%s\" because it doesn't have an email address\n",
+        gdata_gd_name_get_full_name(gdata_contacts_contact_get_name(contact)));
+  }
+  return retval;
+}
+
+static void free_contact(Contact *contact)
+{
+  g_free((gpointer)contact->full_name);
+  g_free((gpointer)contact->family_name);
+  g_free((gpointer)contact->given_name);
+  g_free((gpointer)contact->address);
+  g_free(contact);
+}
+
+static void clear_contacts_cache(void)
+{
+  GSList *walk;
+  for(walk = contacts_cache.contacts; walk; walk = walk->next)
+    free_contact(walk->data);
+  g_slist_free(contacts_cache.contacts);
+  contacts_cache.contacts = NULL;
+}
+
+static void cm_gdata_query_contacts_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
+{
+  GDataFeed *feed;
+  GList *walk;
+  GError *error = NULL;
+  guint num_contacts = 0;
+  guint num_contacts_added = 0;
+       gchar *tmpstr1, *tmpstr2;
+
+  feed = gdata_service_query_finish(GDATA_SERVICE(service), res, &error);
+  cm_gdata_contacts_query_running = FALSE;
+  if(error)
+  {
+    g_object_unref(feed);
+    log_error(LOG_PROTOCOL, _("GData plugin: Error querying for contacts: %s\n"), error->message);
+    g_error_free(error);
+    return;
+  }
+
+  /* clear cache */
+  clear_contacts_cache();
+
+  /* Iterate through the returned contacts and fill the cache */
+  for(walk = gdata_feed_get_entries(feed); walk; walk = walk->next) {
+    num_contacts_added += add_gdata_contact_to_cache(GDATA_CONTACTS_CONTACT(walk->data));
+    num_contacts++;
+  }
+  g_object_unref(feed);
+  contacts_cache.contacts = g_slist_reverse(contacts_cache.contacts);
+       /* i18n: First part of "Added X of Y contacts to cache" */
+  tmpstr1 = g_strdup_printf(ngettext("Added %d of", "Added %d of", num_contacts_added), num_contacts_added);
+       /* i18n: Second part of "Added X of Y contacts to cache" */
+  tmpstr2 = g_strdup_printf(ngettext("1 contact to the cache", "%d contacts to the cache", num_contacts), num_contacts);
+  log_message(LOG_PROTOCOL, "%s %s\n", tmpstr1, tmpstr2);
+       g_free(tmpstr1);
+       g_free(tmpstr2);
+}
+
+static void query_after_auth(GDataContactsService *service)
+{
+  GDataContactsQuery *query;
+
+  log_message(LOG_PROTOCOL, _("GData plugin: Starting async contacts query\n"));
+
+  query = gdata_contacts_query_new(NULL);
+  gdata_contacts_query_set_group(query, contacts_group_id);
+  gdata_query_set_max_results(GDATA_QUERY(query), cm_gdata_config.max_num_results);
+  gdata_contacts_service_query_contacts_async(service, GDATA_QUERY(query), NULL, NULL, NULL,
+#ifdef HAVE_GDATA_VERSION_0_9_1
+  NULL,
+#endif
+  (GAsyncReadyCallback)cm_gdata_query_contacts_ready, NULL);
+
+  g_object_unref(query);
+}
+
+#ifdef HAVE_GDATA_VERSION_0_9_1
+static void cm_gdata_query_groups_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
+{
+  GDataFeed *feed;
+  GList *walk;
+  GError *error = NULL;
+
+  feed = gdata_service_query_finish(GDATA_SERVICE(service), res, &error);
+  if(error)
+  {
+    g_object_unref(feed);
+    log_error(LOG_PROTOCOL, _("GData plugin: Error querying for groups: %s\n"), error->message);
+    g_error_free(error);
+    return;
+  }
+
+  /* Iterate through the returned groups and search for Contacts group id */
+  for(walk = gdata_feed_get_entries(feed); walk; walk = walk->next) {
+    const gchar *system_group_id;
+    GDataContactsGroup *group = GDATA_CONTACTS_GROUP(walk->data);
+
+    system_group_id = gdata_contacts_group_get_system_group_id(group);
+    if(system_group_id && !strcmp(system_group_id, GDATA_CONTACTS_GROUP_CONTACTS)) {
+      gchar *pos;
+      const gchar *id;
+
+      id = gdata_entry_get_id(GDATA_ENTRY(group));
+
+      /* possibly replace projection "full" by "base" */
+      pos = g_strrstr(id, "/full/");
+      if(pos) {
+        GString *str = g_string_new("\0");
+        int off = pos-id;
+
+        g_string_append_len(str, id, off);
+        g_string_append(str, "/base/");
+        g_string_append(str, id+off+strlen("/full/"));
+        g_string_append_c(str, '\0');
+        contacts_group_id = str->str;
+        g_string_free(str, FALSE);
+      }
+      else
+        contacts_group_id = g_strdup(id);
+      break;
+    }
+  }
+  g_object_unref(feed);
+
+  log_message(LOG_PROTOCOL, _("GData plugin: Groups received\n"));
+
+  query_after_auth(service);
+}
+#endif
+
+#ifdef HAVE_GDATA_VERSION_0_9
+static void query_for_contacts_group_id(GDataClientLoginAuthorizer *authorizer)
+{
+  GDataContactsService *service;
+#ifdef HAVE_GDATA_VERSION_0_9_1
+
+  log_message(LOG_PROTOCOL, _("GData plugin: Starting async groups query\n"));
+
+  service = gdata_contacts_service_new(GDATA_AUTHORIZER(authorizer));
+  gdata_contacts_service_query_groups_async(service, NULL, NULL, NULL, NULL, NULL,
+      (GAsyncReadyCallback)cm_gdata_query_groups_ready, NULL);
+#else
+  service = gdata_contacts_service_new(GDATA_AUTHORIZER(authorizer));
+  query_after_auth(service);
+#endif
+  g_object_unref(service);
+}
+
+static void cm_gdata_auth_ready(GDataClientLoginAuthorizer *authorizer, GAsyncResult *res, gpointer data)
+{
+  GError *error = NULL;
+
+  if(gdata_client_login_authorizer_authenticate_finish(authorizer, res, &error) == FALSE)
+  {
+    log_error(LOG_PROTOCOL, _("GData plugin: Authentication error: %s\n"), error->message);
+    g_error_free(error);
+    cm_gdata_contacts_query_running = FALSE;
+    return;
+  }
+
+  log_message(LOG_PROTOCOL, _("GData plugin: Authenticated\n"));
+
+  if(!contacts_group_id)
+  {
+    query_for_contacts_group_id(authorizer);
+  }
+  else {
+    GDataContactsService *service;
+    service = gdata_contacts_service_new(GDATA_AUTHORIZER(authorizer));
+    query_after_auth(service);
+    g_object_unref(service);
+  }
+}
+#else
+static void cm_gdata_auth_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
+{
+  GError *error = NULL;
+
+  if(!gdata_service_authenticate_finish(GDATA_SERVICE(service), res, &error))
+  {
+    log_error(LOG_PROTOCOL, _("GData plugin: Authentication error: %s\n"), error->message);
+    g_error_free(error);
+    cm_gdata_contacts_query_running = FALSE;
+    return;
+  }
+
+  log_message(LOG_PROTOCOL, _("GData plugin: Authenticated\n"));
+
+  query_after_auth(service);
+}
+#endif
+static void query()
+{
+
+#ifdef HAVE_GDATA_VERSION_0_9
+  GDataClientLoginAuthorizer *authorizer;
+#else
+  GDataContactsService *service;
+#endif
+
+  if(cm_gdata_contacts_query_running)
+  {
+    debug_print("GData plugin: Network query already in progress");
+    return;
+  }
+
+  log_message(LOG_PROTOCOL, _("GData plugin: Starting async authentication\n"));
+
+#ifdef HAVE_GDATA_VERSION_0_9
+  authorizer = gdata_client_login_authorizer_new(CM_GDATA_CLIENT_ID, GDATA_TYPE_CONTACTS_SERVICE);
+  gdata_client_login_authorizer_authenticate_async(authorizer, cm_gdata_config.username, cm_gdata_config.password, NULL, (GAsyncReadyCallback)cm_gdata_auth_ready, NULL);
+  cm_gdata_contacts_query_running = TRUE;
+#else
+  service = gdata_contacts_service_new(CM_GDATA_CLIENT_ID);
+  cm_gdata_contacts_query_running = TRUE;
+  gdata_service_authenticate_async(GDATA_SERVICE(service), cm_gdata_config.username, cm_gdata_config.password, NULL,
+      (GAsyncReadyCallback)cm_gdata_auth_ready, NULL);
+#endif
+
+
+#ifdef HAVE_GDATA_VERSION_0_9
+  g_object_unref(authorizer);
+#else
+  g_object_unref(service);
+#endif
+
+}
+
+
+static void add_contacts_to_list(GList **address_list, GSList *contacts)
+{
+  GSList *walk;
+
+  for(walk = contacts; walk; walk = walk->next)
+  {
+    address_entry *ae;
+    Contact *contact = walk->data;
+
+    ae = g_new0(address_entry, 1);
+    ae->name = g_strdup(contact->full_name);
+    ae->address = g_strdup(contact->address);
+    ae->grp_emails = NULL;
+
+    *address_list = g_list_prepend(*address_list, ae);
+    addr_compl_add_address1(ae->address, ae);
+
+    if(contact->given_name && *(contact->given_name) != '\0')
+      addr_compl_add_address1(contact->given_name, ae);
+
+    if(contact->family_name && *(contact->family_name) != '\0')
+      addr_compl_add_address1(contact->family_name, ae);
+  }
+}
+
+void cm_gdata_add_contacts(GList **address_list)
+{
+  add_contacts_to_list(address_list, contacts_cache.contacts);
+}
+
+gboolean cm_gdata_update_contacts_cache(void)
+{
+  if(prefs_common.work_offline)
+  {
+    debug_print("GData plugin: Offline mode\n");
+  }
+  else if(!cm_gdata_config.username || *(cm_gdata_config.username) == '\0' || !cm_gdata_config.password)
+  {
+    /* noop if no credentials are given */
+    debug_print("GData plugin: Empty username or password\n");
+  }
+  else
+  {
+    debug_print("GData plugin: Querying contacts");
+    query();
+  }
+  return TRUE;
+}
+
+void cm_gdata_contacts_done(void)
+{
+  g_free(contacts_group_id);
+  contacts_group_id = NULL;
+
+  write_cache_to_file();
+  if(contacts_cache.contacts && !claws_is_exiting())
+    clear_contacts_cache();
+}
+
+void cm_gdata_load_contacts_cache_from_file(void)
+{
+  gchar *path;
+  GNode *rootnode, *childnode, *contactnode;
+  XMLNode *xmlnode;
+
+  path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, GDATA_CONTACTS_FILENAME, NULL);
+  if(!is_file_exist(path)) {
+    g_free(path);
+    return;
+  }
+
+  /* no merging; make sure the cache is empty (this should be a noop, but just to be safe...) */
+  clear_contacts_cache();
+
+  rootnode = xml_parse_file(path);
+  g_free(path);
+  if(!rootnode)
+    return;
+  xmlnode = rootnode->data;
+
+  /* Check that root entry is "gdata" */
+  if(strcmp2(xmlnode->tag->tag, "gdata") != 0) {
+    g_warning("wrong gdata cache file\n");
+    xml_free_tree(rootnode);
+    return;
+  }
+
+  for(childnode = rootnode->children; childnode; childnode = childnode->next) {
+    GList *attributes;
+    xmlnode = childnode->data;
+
+    if(strcmp2(xmlnode->tag->tag, "contacts") != 0)
+      continue;
+
+    for(contactnode = childnode->children; contactnode; contactnode = contactnode->next)
+    {
+      Contact *cached_contact;
+
+      xmlnode = contactnode->data;
+
+      cached_contact = g_new0(Contact, 1);
+      /* Attributes of the branch nodes */
+      for(attributes = xmlnode->tag->attr; attributes; attributes = attributes->next) {
+        XMLAttr *attr = attributes->data;
+
+        if(attr && attr->name && attr->value) {
+          if(!strcmp2(attr->name, "full_name"))
+            cached_contact->full_name = g_strdup(attr->value);
+          else if(!strcmp2(attr->name, "given_name"))
+            cached_contact->given_name = g_strdup(attr->value);
+          else if(!strcmp2(attr->name, "family_name"))
+            cached_contact->family_name = g_strdup(attr->value);
+          else if(!strcmp2(attr->name, "address"))
+            cached_contact->address = g_strdup(attr->value);
+        }
+      }
+      contacts_cache.contacts = g_slist_prepend(contacts_cache.contacts, cached_contact);
+      debug_print("Read contact from cache: %s\n", cached_contact->full_name);
+    }
+  }
+
+  /* Free XML tree */
+  xml_free_tree(rootnode);
+
+  contacts_cache.contacts = g_slist_reverse(contacts_cache.contacts);
+}
diff --git a/src/plugins/gdata/cm_gdata_contacts.h b/src/plugins/gdata/cm_gdata_contacts.h
new file mode 100644 (file)
index 0000000..1be697c
--- /dev/null
@@ -0,0 +1,28 @@
+/* GData plugin for Claws-Mail
+ * Copyright (C) 2011 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CM_GDATA_CONTACTS_H
+#define CM_GDATA_CONTACTS_H
+
+#include <glib.h>
+
+void cm_gdata_add_contacts(GList **address_list);
+void cm_gdata_contacts_done(void);
+gboolean cm_gdata_update_contacts_cache(void);
+void cm_gdata_load_contacts_cache_from_file(void);
+
+#endif /* CM_GDATA_CONTACTS_H */
diff --git a/src/plugins/gdata/cm_gdata_prefs.c b/src/plugins/gdata/cm_gdata_prefs.c
new file mode 100644 (file)
index 0000000..5098a5b
--- /dev/null
@@ -0,0 +1,161 @@
+/* GData plugin for Claws-Mail
+ * Copyright (C) 2011 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#  include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "cm_gdata_prefs.h"
+#include "gdata_plugin.h"
+#include "cm_gdata_contacts.h"
+
+#include "prefs_gtk.h"
+#include "main.h"
+
+#include <gtk/gtk.h>
+
+
+typedef struct
+{
+  PrefsPage page;
+  GtkWidget *entry_username;
+  GtkWidget *entry_password;
+  GtkWidget *spin_max_num_results;
+  GtkWidget *spin_max_cache_age;
+} CmGDataPage;
+
+CmGDataPrefs cm_gdata_config;
+CmGDataPage gdata_page;
+
+PrefParam cm_gdata_param[] =
+{
+    {"username", NULL, &cm_gdata_config.username, P_STRING,
+        &gdata_page.entry_username, prefs_set_data_from_entry, prefs_set_entry},
+
+    {"password", NULL, &cm_gdata_config.password, P_PASSWORD,
+        &gdata_page.entry_password, prefs_set_data_from_entry, prefs_set_entry},
+
+    { "max_num_results", "1000", &cm_gdata_config.max_num_results, P_INT,
+        &gdata_page.spin_max_num_results, prefs_set_data_from_spinbtn, prefs_set_spinbtn},
+
+    { "max_cache_age", "300", &cm_gdata_config.max_cache_age, P_INT,
+        &gdata_page.spin_max_cache_age, prefs_set_data_from_spinbtn, prefs_set_spinbtn},
+
+    {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL }
+};
+
+static void gdata_create_prefs_page(PrefsPage *page, GtkWindow *window, gpointer data)
+{
+  GtkWidget *vbox;
+  GtkWidget *frame;
+  GtkWidget *spinner;
+  GtkWidget *table;
+  GtkWidget *label;
+  GtkWidget *entry;
+
+  vbox = gtk_vbox_new(FALSE, 0);
+
+  /* auth frame */
+  frame = gtk_frame_new(_("Authentication"));
+  gtk_container_set_border_width(GTK_CONTAINER(frame), 5);
+  gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
+
+  /* username */
+  table = gtk_table_new(2, 2, FALSE);
+  label = gtk_label_new(_("Username:"));
+  gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+  gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 4, 4);
+  entry = gtk_entry_new();
+  gtk_widget_set_size_request(entry, 250, -1);
+  gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 4, 4);
+  gdata_page.entry_username = entry;
+  label = gtk_label_new(_("Password:"));
+  gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+  gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 4, 4);
+  entry = gtk_entry_new();
+  gtk_widget_set_size_request(entry, 250, -1);
+  gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+  gdata_page.entry_password = entry;
+  gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 4, 4);
+  gtk_container_add(GTK_CONTAINER(frame), table);
+
+  table = gtk_table_new(2, 2, FALSE);
+  gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
+  label = gtk_label_new(_("Polling interval (seconds):"));
+  gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 4, 4);
+  gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+  spinner = gtk_spin_button_new_with_range(10, 10000, 10);
+  gtk_table_attach(GTK_TABLE(table), spinner, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 4, 4);
+  gdata_page.spin_max_cache_age = spinner;
+
+  label = gtk_label_new(_("Maximum number of results:"));
+  gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 4, 4);
+  gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+  spinner = gtk_spin_button_new_with_range(0, G_MAXINT, 50);
+  gtk_table_attach(GTK_TABLE(table), spinner, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 4, 4);
+  gdata_page.spin_max_num_results = spinner;
+
+  gtk_widget_show_all(vbox);
+  page->widget = vbox;
+
+  prefs_set_dialog(cm_gdata_param);
+}
+
+static void gdata_destroy_prefs_page(PrefsPage *page)
+{
+}
+
+static void gdata_save_prefs(PrefsPage *page)
+{
+  int old_max_cache_age = cm_gdata_config.max_cache_age;
+
+  if (!page->page_open)
+    return;
+
+  prefs_set_data_from_dialog(cm_gdata_param);
+
+  cm_gdata_update_contacts_cache();
+  if(old_max_cache_age != cm_gdata_config.max_cache_age)
+    cm_gdata_update_contacts_update_timer();
+}
+
+void cm_gdata_prefs_init(void)
+{
+  static gchar *path[3];
+
+  path[0] = _("Plugins");
+  path[1] = _("GData");
+  path[2] = NULL;
+
+  gdata_page.page.path = path;
+  gdata_page.page.create_widget = gdata_create_prefs_page;
+  gdata_page.page.destroy_widget = gdata_destroy_prefs_page;
+  gdata_page.page.save_page = gdata_save_prefs;
+  prefs_gtk_register_page((PrefsPage*) &gdata_page);
+}
+
+void cm_gdata_prefs_done(void)
+{
+  if(!claws_is_exiting()) {
+    prefs_gtk_unregister_page((PrefsPage*) &gdata_page);
+  }
+}
diff --git a/src/plugins/gdata/cm_gdata_prefs.h b/src/plugins/gdata/cm_gdata_prefs.h
new file mode 100644 (file)
index 0000000..533bf00
--- /dev/null
@@ -0,0 +1,36 @@
+/* GData plugin for Claws-Mail
+ * Copyright (C) 2011 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CM_GDATA_PREFS_H_
+#define CM_GDATA_PREFS_H_
+
+#include "prefs_gtk.h"
+
+typedef struct {
+  char *username;
+  char *password;
+  int max_num_results;
+  int max_cache_age;
+} CmGDataPrefs;
+
+extern CmGDataPrefs cm_gdata_config;
+extern PrefParam    cm_gdata_param[];
+
+void cm_gdata_prefs_init(void);
+void cm_gdata_prefs_done(void);
+
+#endif /* CM_GDATA_PREFS_H_ */
diff --git a/src/plugins/gdata/gdata_plugin.c b/src/plugins/gdata/gdata_plugin.c
new file mode 100644 (file)
index 0000000..5985f1d
--- /dev/null
@@ -0,0 +1,190 @@
+/* GData plugin for Claws-Mail
+ * Copyright (C) 2011 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#  include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#ifdef G_OS_UNIX
+# include <libintl.h>
+#endif
+
+#include "common/plugin.h"
+#include "common/version.h"
+#include "common/utils.h"
+#include "common/hooks.h"
+#include "common/defs.h"
+#include "common/prefs.h"
+#include "main.h"
+#include "mainwindow.h"
+#include "addr_compl.h"
+
+#include "cm_gdata_contacts.h"
+#include "cm_gdata_prefs.h"
+
+static guint hook_address_completion;
+static guint hook_offline_switch;
+static guint timer_query_contacts = 0;
+
+static gboolean my_address_completion_build_list_hook(gpointer source, gpointer data)
+{
+  cm_gdata_add_contacts(source);
+  return FALSE;
+}
+
+static gboolean my_offline_switch_hook(gpointer source, gpointer data)
+{
+  cm_gdata_update_contacts_cache();
+  return FALSE;
+}
+
+static void cm_gdata_save_config(void)
+{
+  PrefFile *pfile;
+  gchar *rcpath;
+
+  debug_print("Saving GData plugin configuration...\n");
+
+  rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+  pfile = prefs_write_open(rcpath);
+  g_free(rcpath);
+  if (!pfile || (prefs_set_block_label(pfile, "GDataPlugin") < 0))
+    return;
+
+  if (prefs_write_param(cm_gdata_param, pfile->fp) < 0) {
+    debug_print("failed!\n");
+    g_warning(_("\nGData Plugin: Failed to write plugin configuration to file\n"));
+    prefs_file_close_revert(pfile);
+    return;
+  }
+  if (fprintf(pfile->fp, "\n") < 0) {
+    FILE_OP_ERROR(rcpath, "fprintf");
+    prefs_file_close_revert(pfile);
+  }
+  else
+    prefs_file_close(pfile);
+  debug_print("done.\n");
+}
+
+void cm_gdata_update_contacts_update_timer(void)
+{
+  if(timer_query_contacts != 0)
+    g_source_remove(timer_query_contacts);
+  timer_query_contacts = g_timeout_add_seconds(cm_gdata_config.max_cache_age, (GSourceFunc)cm_gdata_update_contacts_cache, NULL);
+}
+
+gint plugin_init(gchar **error)
+{
+  gchar *rcpath;
+
+  /* Version check */
+  if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,1,55),
+                          VERSION_NUMERIC, _("GData"), error))
+    return -1;
+
+  hook_address_completion = hooks_register_hook(ADDDRESS_COMPLETION_BUILD_ADDRESS_LIST_HOOKLIST,
+      my_address_completion_build_list_hook, NULL);
+  if(hook_address_completion == (guint) -1) {
+    *error = g_strdup(_("Failed to register address completion hook in the GData plugin"));
+    return -1;
+  }
+
+  hook_offline_switch = hooks_register_hook(OFFLINE_SWITCH_HOOKLIST, my_offline_switch_hook, NULL);
+  if(hook_offline_switch == (guint) -1) {
+    hooks_unregister_hook(ADDDRESS_COMPLETION_BUILD_ADDRESS_LIST_HOOKLIST, hook_address_completion);
+    *error = g_strdup(_("Failed to register offline switch hook in the GData plugin"));
+    return -1;
+  }
+
+  /* Configuration */
+  prefs_set_default(cm_gdata_param);
+  rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+  prefs_read_config(cm_gdata_param, "GDataPlugin", rcpath, NULL);
+  g_free(rcpath);
+
+  cm_gdata_prefs_init();
+
+  /* contacts cache */
+  cm_gdata_load_contacts_cache_from_file();
+  cm_gdata_update_contacts_update_timer();
+  cm_gdata_update_contacts_cache();
+
+  debug_print("GData plugin loaded\n");
+
+  return 0;
+}
+
+gboolean plugin_done(void)
+{
+  if(!claws_is_exiting()) {
+    hooks_unregister_hook(ADDDRESS_COMPLETION_BUILD_ADDRESS_LIST_HOOKLIST, hook_address_completion);
+    hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, hook_offline_switch);
+    g_source_remove(timer_query_contacts);
+  }
+  cm_gdata_prefs_done();
+  cm_gdata_contacts_done();
+
+  cm_gdata_save_config();
+
+  debug_print("GData plugin unloaded\n");
+
+  /* returning FALSE because dependant libraries may not be unload-safe. */
+  return FALSE;
+}
+
+const gchar *plugin_name(void)
+{
+  return _("GData");
+}
+
+const gchar *plugin_desc(void)
+{
+  return _("This plugin provides access to the GData protocol "
+          "for Claws Mail.\n\n"
+      "The GData protocol is an interface to Google services.\n"
+      "Currently, the only implemented functionality is to include "
+      "Google Contacts into the Tab-address completion.\n"
+     "\nFeedback to <berndth@gmx.de> is welcome.");
+}
+
+const gchar *plugin_type(void)
+{
+  return "GTK2";
+}
+
+const gchar *plugin_licence(void)
+{
+  return "GPL3+";
+}
+
+const gchar *plugin_version(void)
+{
+  return VERSION;
+}
+
+struct PluginFeature *plugin_provides(void)
+{
+  static struct PluginFeature features[] =
+    { {PLUGIN_UTILITY, N_("GData integration")},
+      {PLUGIN_NOTHING, NULL}};
+  return features;
+}
diff --git a/src/plugins/gdata/gdata_plugin.h b/src/plugins/gdata/gdata_plugin.h
new file mode 100644 (file)
index 0000000..eb9c363
--- /dev/null
@@ -0,0 +1,23 @@
+/* GData plugin for Claws-Mail
+ * Copyright (C) 2011 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_PLUGIN_H_
+#define GDATA_PLUGIN_H_
+
+void cm_gdata_update_contacts_update_timer(void);
+
+#endif /* GDATA_PLUGIN_H_ */
diff --git a/src/plugins/gdata/placeholder.txt b/src/plugins/gdata/placeholder.txt
deleted file mode 100644 (file)
index 3b94f91..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Placeholder
diff --git a/src/plugins/geolocation/placeholder.txt b/src/plugins/geolocation/placeholder.txt
deleted file mode 100644 (file)
index 3b94f91..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Placeholder
diff --git a/src/plugins/gtkhtml2_viewer/placeholder.txt b/src/plugins/gtkhtml2_viewer/placeholder.txt
deleted file mode 100644 (file)
index 3b94f91..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Placeholder
diff --git a/src/plugins/mailmbox/Makefile.am b/src/plugins/mailmbox/Makefile.am
new file mode 100644 (file)
index 0000000..234f602
--- /dev/null
@@ -0,0 +1,42 @@
+plugindir = $(pkglibdir)/plugins
+plugin_LTLIBRARIES = mailmbox.la
+
+## Mailmbox folder plugin
+
+mailmbox_la_SOURCES = \
+       plugin.c plugin_gtk.c \
+       carray.c carray.h \
+       chash.c chash.h \
+       clist.c clist.h \
+       mailimf.c mailimf.h \
+       mailimf_types.c mailimf_types.h \
+       mailimf_types_helper.c mailimf_types_helper.h \
+       mailimf_write.c mailimf_write.h \
+       maillock.c maillock.h \
+       mailmbox.c mailmbox.h \
+       mailmbox_folder.c mailmbox_folder.h \
+       mailmbox_parse.c mailmbox_parse.h \
+       mailmbox_types.c mailmbox_types.h \
+       mmapstring.c mmapstring.h \
+       plugin_gtk.h
+
+mailmbox_la_LDFLAGS = \
+       -avoid-version -module
+
+mailmbox_la_LIBADD = \
+       $(GTK_LIBS)
+
+mailmbox_la_CPPFLAGS = \
+       $(CLAWS_MAIL_CFLAGS) \
+       $(GLIB_CFLAGS) \
+       $(GTK_CFLAGS)
+
+AM_CPPFLAGS = \
+       $(CLAWS_MAIL_CFLAGS) \
+       $(GLIB_CFLAGS) \
+       $(GTK_CFLAGS)
+
+INCLUDES = \
+       -I$(top_srcdir)/src \
+       -I$(top_srcdir)/src/common \
+       -I$(top_srcdir)/src/gtk
diff --git a/src/plugins/mailmbox/carray.c b/src/plugins/mailmbox/carray.c
new file mode 100644 (file)
index 0000000..a8e78c9
--- /dev/null
@@ -0,0 +1,143 @@
+
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * carray - Implements simple dynamic pointer arrays
+ *
+ * Copyright (c) 1999-2000, Gaël Roualland <gael.roualland@iname.com>
+ * interface changes - 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "carray.h"
+
+carray * carray_new(unsigned int initsize) {
+  carray * array;
+
+  array = (carray *) malloc(sizeof(carray));
+  if (!array) return NULL;
+
+  array->len = 0;
+  array->max = initsize;
+  array->array = (void **) malloc(sizeof(void *) * initsize);
+  if (!array->array) {
+    free(array);
+    return NULL;
+  }
+  return array;
+}
+
+int carray_add(carray * array, void * data, unsigned int * index) {
+  int r;
+  
+  r = carray_set_size(array, array->len + 1);
+  if (r < 0)
+    return r;
+
+  array->array[array->len - 1] = data;
+  if (index != NULL)
+    * index = array->len - 1;
+
+  return 0;
+}
+
+int carray_set_size(carray * array, unsigned int new_size)
+{
+  if (new_size > array->max) {
+    unsigned int n = array->max * 2;
+    void * new;
+
+    while (n <= new_size)
+      n *= 2;
+
+    new = (void **) realloc(array->array, sizeof(void *) * n);
+    if (!new)
+      return -1;
+    array->array = new;
+    array->max = n;
+  }
+  array->len = new_size;
+
+  return 0;
+}
+
+int carray_delete_fast(carray * array, unsigned int indx) {
+  if (indx >= array->len)
+    return -1;
+
+  array->array[indx] = NULL;
+
+  return 0;
+}
+
+int carray_delete(carray * array, unsigned int indx) {
+  if (indx >= array->len)
+    return -1;
+
+  if (indx != --array->len)
+    array->array[indx] = array->array[array->len];
+  return 0;
+}
+
+int carray_delete_slow(carray * array, unsigned int indx) {
+  if (indx >= array->len)
+    return -1;
+
+  if (indx != --array->len) 
+    memmove(array->array + indx, array->array + indx + 1,
+           (array->len - indx) * sizeof(void *));
+  return 0;
+}
+
+#ifdef NO_MACROS
+void ** carray_data(carray * array) {
+  return array->array;
+}
+
+unsigned int carray_count(carray * array) {
+  return array->len;
+}
+
+void * carray_get(carray * array, unsigned int indx) {
+  return array->array[indx];
+}
+
+void carray_set(carray * array, unsigned int indx, void * value) {
+  array->array[indx] = value;
+}
+#endif
+
+void carray_free(carray * array) {
+  free(array->array);
+  free(array);
+}
diff --git a/src/plugins/mailmbox/carray.h b/src/plugins/mailmbox/carray.h
new file mode 100644 (file)
index 0000000..68858e3
--- /dev/null
@@ -0,0 +1,126 @@
+
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * carray - Implements simple dynamic pointer arrays
+ *
+ * Copyright (c) 1999-2000, Gaël Roualland <gael.roualland@iname.com>
+ * interface changes - 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef CARRAY_H
+#define CARRAY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <inttypes.h>
+
+struct carray_s {
+  void ** array;
+  unsigned int len;
+  unsigned int max;
+};
+
+typedef struct carray_s carray;
+
+/* Creates a new array of pointers, with initsize preallocated cells */
+carray *   carray_new(unsigned int initsize);
+
+/* Adds the pointer to data in the array.
+   Returns the index of the pointer in the array or -1 on error */
+int       carray_add(carray * array, void * data, unsigned int * index);
+
+int carray_set_size(carray * array, unsigned int new_size);
+
+/* Removes the cell at this index position. Returns TRUE on success.
+   Order of elements in the array IS changed. */
+int       carray_delete(carray * array, unsigned int indx);
+
+/* Removes the cell at this index position. Returns TRUE on success.
+   Order of elements in the array IS not changed. */
+int       carray_delete_slow(carray * array, unsigned int indx);
+
+/* remove without decreasing the size of the array */
+int carray_delete_fast(carray * array, unsigned int indx);
+
+/* Some of the following routines can be implemented as macros to
+   be faster. If you don't want it, define NO_MACROS */
+#ifdef NO_MACROS
+
+/* Returns the array itself */
+void **   carray_data(carray);
+
+/* Returns the number of elements in the array */
+int       carray_count(carray);
+
+/* Returns the contents of one cell */
+void *    carray_get(carray array, unsigned int indx);
+
+/* Sets the contents of one cell */
+void      carray_set(carray array, unsigned int indx, void * value);
+
+#else
+
+#if 0
+#define   carray_data(a)         (a->array)
+#define   carray_count(a)        (a->len)
+#define   carray_get(a, indx)    (a->array[indx])
+#define   carray_set(a, indx, v) do { a->array[indx]=v; } while(0)
+#endif
+
+static inline void ** carray_data(carray * array) {
+  return array->array;
+}
+
+static inline unsigned int carray_count(carray * array) {
+  return array->len;
+}
+
+static inline void * carray_get(carray * array, unsigned int indx) {
+  return array->array[indx];
+}
+
+static inline void carray_set(carray * array,
+    unsigned int indx, void * value) {
+  array->array[indx] = value;
+}
+#endif
+
+void carray_free(carray * array);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/plugins/mailmbox/chash.c b/src/plugins/mailmbox/chash.c
new file mode 100644 (file)
index 0000000..2055221
--- /dev/null
@@ -0,0 +1,395 @@
+
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * chash - Implements generic hash tables.
+ *
+ * Copyright (c) 1999-2000, Gaël Roualland <gael.roualland@iname.com>
+ * interface changes - 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "chash.h"
+
+/* This defines the maximum (average) number of entries per bucket.
+   The hash is resized everytime inserting an entry makes the
+   average go over that value. */
+#define CHASH_MAXDEPTH    3
+
+static inline unsigned int chash_func(const char * key, unsigned int len) {
+#if 0
+  register unsigned int c = 0, t;
+  register const char * k = key;
+  
+  while (len--) {
+    c += (c << 4) + *k++;
+    if ((t = c & 0xF0000000)) {
+      c ^= t >> 24;
+      c ^= t;
+    }
+  }
+  return c;
+#endif
+  register unsigned int c = 5381;
+  register const char * k = key;
+  
+  while (len--) {
+    c = ((c << 5) + c) + *k++;
+  }
+  
+  return c;
+}
+
+static inline char * chash_dup(const void * data, unsigned int len)
+{
+  void * r;
+
+  r = (char *) malloc(len);
+  if (!r)
+    return NULL;
+  memcpy(r, data, len);
+  return r;
+}
+
+chash * chash_new(unsigned int size, int flags)
+{
+  chash * h;
+
+  h = (chash *) malloc(sizeof(chash));
+  if (h == NULL)
+    return NULL;
+
+  h->count = 0;
+  h->cells = (struct chashcell **) calloc(size, sizeof(struct chashcell *));
+  if (h->cells == NULL) {
+    free(h);
+    return NULL;
+  }
+  h->size = size;
+  h->copykey = flags & CHASH_COPYKEY;
+  h->copyvalue = flags & CHASH_COPYVALUE;
+  
+  return h;
+}
+
+int chash_get(chash * hash,
+             chashdatum * key, chashdatum * result)
+{
+  unsigned int func;
+  chashiter * iter;
+  
+  func = chash_func(key->data, key->len);
+
+  /* look for the key in existing cells */
+  iter = hash->cells[func % hash->size];
+  while (iter) {
+    if (iter->key.len == key->len && iter->func == func
+       && !memcmp(iter->key.data, key->data, key->len)) {
+      * result = iter->value; /* found */
+
+      return 0;
+    }
+    iter = iter->next;
+  }
+
+  return -1;
+}
+
+int chash_set(chash * hash,
+             chashdatum * key,
+             chashdatum * value,
+             chashdatum * oldvalue)
+{
+  unsigned int func, indx;
+  chashiter * iter, * cell;
+  int r;
+
+  if (hash->count > hash->size * CHASH_MAXDEPTH) {
+    r = chash_resize(hash, (hash->count / CHASH_MAXDEPTH) * 2 + 1);
+    if (r < 0)
+      goto err;
+  }
+
+  func = chash_func(key->data, key->len);
+  indx = func % hash->size;
+
+  /* look for the key in existing cells */
+  iter = hash->cells[indx];
+  while (iter) {
+    if (iter->key.len == key->len && iter->func == func
+       && !memcmp(iter->key.data, key->data, key->len)) {
+      /* found, replacing entry */
+      if (hash->copyvalue) {
+       char * data;
+
+       data = chash_dup(value->data, value->len);
+       if (data == NULL)
+         goto err;
+
+       free(iter->value.data);
+       iter->value.data = data;
+       iter->value.len = value->len;
+      } else {
+       if (oldvalue != NULL) {
+         oldvalue->data = iter->value.data;
+         oldvalue->len = iter->value.len;
+       }
+       iter->value.data = value->data;
+       iter->value.len = value->len;
+      }
+      if (!hash->copykey)
+       iter->key.data = key->data;
+
+      if (oldvalue != NULL) {
+       oldvalue->data = value->data;
+       oldvalue->len = value->len;
+      }
+
+      return 0;
+    }
+    iter = iter->next;
+  }
+  
+  if (oldvalue != NULL) {
+    oldvalue->data = NULL;
+    oldvalue->len = 0;
+  }
+  
+  /* not found, adding entry */
+  cell = (struct chashcell *) malloc(sizeof(struct chashcell));
+  if (cell == NULL)
+    goto err;
+
+  if (hash->copykey) {
+    cell->key.data = chash_dup(key->data, key->len);
+    if (cell->key.data == NULL)
+      goto free;
+  }
+  else
+    cell->key.data = key->data;
+
+  cell->key.len = key->len;
+  if (hash->copyvalue) {
+    cell->value.data = chash_dup(value->data, value->len);
+    if (cell->value.data == NULL)
+      goto free_key_data;
+  }
+  else
+    cell->value.data = value->data;
+
+  cell->value.len = value->len;
+  cell->func = func;
+  cell->next = hash->cells[indx];
+  hash->cells[indx] = cell;
+  hash->count++;
+
+  return 0;
+  
+ free_key_data:
+  if (hash->copykey)
+    free(cell->key.data);
+ free:
+  free(cell);
+ err:
+  return -1;
+}
+
+int chash_delete(chash * hash, chashdatum * key, chashdatum * oldvalue)
+{
+  /*  chashdatum result = { NULL, TRUE }; */
+  unsigned int func, indx;
+  chashiter * iter, * old;
+
+  /*  
+  if (!keylen)
+    keylen = strlen(key) + 1;  
+  */
+
+  func = chash_func(key->data, key->len);
+  indx = func % hash->size;
+
+  /* look for the key in existing cells */
+  old = NULL;
+  iter = hash->cells[indx];
+  while (iter) {
+    if (iter->key.len == key->len && iter->func == func
+       && !memcmp(iter->key.data, key->data, key->len)) {
+      /* found, deleting */
+      if (old)
+       old->next = iter->next;
+      else
+       hash->cells[indx] = iter->next;
+      if (hash->copykey)
+       free(iter->key.data);
+      if (hash->copyvalue)
+       free(iter->value.data);
+      else {
+       if (oldvalue != NULL) {
+         oldvalue->data = iter->value.data;
+         oldvalue->len = iter->value.len;
+       }
+      }
+      free(iter);
+      hash->count--;
+      return 0;
+    }
+    old = iter;
+    iter = iter->next;
+  }
+
+  return -1; /* not found */
+}
+
+void chash_free(chash * hash) {
+  unsigned int indx;
+  chashiter * iter, * next;
+
+  /* browse the hash table */
+  for(indx = 0; indx < hash->size; indx++) {
+    iter = hash->cells[indx];
+    while (iter) {
+      next = iter->next;
+      if (hash->copykey)
+       free(iter->key.data);
+      if (hash->copyvalue)
+       free(iter->value.data);
+      free(iter);
+      iter = next;
+    }
+  }
+  free(hash->cells);
+  free(hash);
+}
+
+void chash_clear(chash * hash) {
+  unsigned int indx;
+  chashiter * iter, * next;
+
+  /* browse the hash table */
+  for(indx = 0; indx < hash->size; indx++) {
+    iter = hash->cells[indx];
+    while (iter) {
+      next = iter->next;
+      if (hash->copykey)
+       free(iter->key.data);
+      if (hash->copyvalue)
+       free(iter->value.data);
+      free(iter);
+      iter = next;
+    }
+  }
+  memset(hash->cells, 0, hash->size * sizeof(* hash->cells));
+  hash->count = 0;
+}
+
+chashiter * chash_begin(chash * hash) {
+  chashiter * iter;
+  unsigned int indx = 0;
+  
+  iter = hash->cells[0];
+  while(!iter) {
+    indx++;
+    if (indx >= hash->size)
+      return NULL;
+    iter = hash->cells[indx];
+  }
+  return iter;
+}
+
+chashiter * chash_next(chash * hash, chashiter * iter) {
+  unsigned int indx;
+
+  if (!iter)
+    return NULL;
+
+  indx = iter->func % hash->size;
+  iter = iter->next;
+
+  while(!iter) {
+    indx++;
+    if (indx >= hash->size)
+      return NULL;
+    iter = hash->cells[indx];
+  }
+  return iter;
+}
+
+int chash_resize(chash * hash, unsigned int size)
+{
+  struct chashcell ** cells;
+  unsigned int indx, nindx;
+  chashiter * iter, * next;
+  
+  if (hash->size == size)
+    return 0;
+
+  cells = (struct chashcell **) calloc(size, sizeof(struct chashcell *));
+  if (!cells)
+    return -1;
+
+  /* browse initial hash and copy items in second hash */
+  for(indx = 0; indx < hash->size; indx++) {
+    iter = hash->cells[indx];
+    while (iter) {
+      next = iter->next;
+      nindx = iter->func % size;
+      iter->next = cells[nindx];
+      cells[nindx] = iter;
+      iter = next;
+    }
+  }
+  free(hash->cells);
+  hash->size = size;
+  hash->cells = cells;
+
+  return 0;
+}
+
+#ifdef NO_MACROS
+int chash_count(chash * hash) {
+  return hash->count;
+}
+
+int chash_size(chash * hash) {
+  return hash->size;
+}
+
+void chash_value(chashiter * iter, chashdatum * result) {
+  * result = iter->value;
+}
+
+void chash_key(chashiter * iter, chashdatum * result) {
+  * result = iter->key;
+}
+#endif
diff --git a/src/plugins/mailmbox/chash.h b/src/plugins/mailmbox/chash.h
new file mode 100644 (file)
index 0000000..3b2b7d3
--- /dev/null
@@ -0,0 +1,166 @@
+
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * chash - Implements generic hash tables.
+ *
+ * Copyright (c) 1999-2000, Gaël Roualland <gael.roualland@iname.com>
+ * interface changes - 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef CHASH_H
+#define CHASH_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+  void * data;
+  unsigned int len;
+} chashdatum;
+
+struct chash {
+  unsigned int size;
+  unsigned int count;
+  int copyvalue;
+  int copykey;
+  struct chashcell ** cells; 
+};
+
+typedef struct chash chash;
+
+struct chashcell {
+  unsigned int func;
+  chashdatum key;
+  chashdatum value;
+  struct chashcell * next;
+};
+
+typedef struct chashcell chashiter;
+
+#define CHASH_COPYNONE    0
+#define CHASH_COPYKEY     1
+#define CHASH_COPYVALUE   2
+#define CHASH_COPYALL     (CHASH_COPYKEY | CHASH_COPYVALUE)
+
+#define CHASH_DEFAULTSIZE 13
+  
+/* Allocates a new (empty) hash using this initial size and the given flags,
+   specifying which data should be copied in the hash.
+    CHASH_COPYNONE  : Keys/Values are not copied.
+    CHASH_COPYKEY   : Keys are dupped and freed as needed in the hash.
+    CHASH_COPYVALUE : Values are dupped and freed as needed in the hash.
+    CHASH_COPYALL   : Both keys and values are dupped in the hash.
+ */
+chash * chash_new(unsigned int size, int flags);
+
+/* Frees a hash */
+void chash_free(chash * hash);
+
+/* Removes all elements from a hash */
+void chash_clear(chash * hash);
+
+/* Adds an entry in the hash table.
+   Length can be 0 if key/value are strings.
+   If an entry already exists for this key, it is replaced, and its value
+   is returned. Otherwise, the data pointer will be NULL and the length
+   field be set to TRUE or FALSe to indicate success or failure. */
+int chash_set(chash * hash,
+             chashdatum * key,
+             chashdatum * value,
+             chashdatum * oldvalue);
+
+/* Retrieves the data associated to the key if it is found in the hash table.
+   The data pointer and the length will be NULL if not found*/
+int chash_get(chash * hash,
+             chashdatum * key, chashdatum * result);
+
+/* Removes the entry associated to this key if it is found in the hash table,
+   and returns its contents if not dupped (otherwise, pointer will be NULL
+   and len TRUE). If entry is not found both pointer and len will be NULL. */
+int chash_delete(chash * hash,
+                chashdatum * key,
+                chashdatum * oldvalue);
+
+/* Resizes the hash table to the passed size. */
+int chash_resize(chash * hash, unsigned int size);
+
+/* Returns an iterator to the first non-empty entry of the hash table */
+chashiter * chash_begin(chash * hash);
+
+/* Returns the next non-empty entry of the hash table */
+chashiter * chash_next(chash * hash, chashiter * iter);
+
+/* Some of the following routines can be implemented as macros to
+   be faster. If you don't want it, define NO_MACROS */
+#ifdef NO_MACROS
+/* Returns the size of the hash table */
+unsigned int          chash_size(chash * hash);
+
+/* Returns the number of entries in the hash table */
+unsigned int          chash_count(chash * hash);
+
+/* Returns the key part of the entry pointed by the iterator */
+void chash_key(chashiter * iter, chashdatum * result);
+
+/* Returns the value part of the entry pointed by the iterator */
+void chash_value(chashiter * iter, chashdatum * result);
+
+#else
+static inline unsigned int chash_size(chash * hash)
+{
+  return hash->size;
+}
+
+static inline unsigned int chash_count(chash * hash)
+{
+  return hash->count;
+}
+
+static inline void chash_key(chashiter * iter, chashdatum * result)
+{
+  * result = iter->key;
+}
+
+static inline void chash_value(chashiter * iter, chashdatum * result)
+{
+  * result = iter->value;
+}
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/plugins/mailmbox/clist.c b/src/plugins/mailmbox/clist.c
new file mode 100644 (file)
index 0000000..e5c680d
--- /dev/null
@@ -0,0 +1,266 @@
+
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * clist - Implements simple generic double-linked pointer lists
+ *
+ * Copyright (c) 1999-2000, Gaël Roualland <gael.roualland@iname.com>
+ * interface changes - 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#include <stdlib.h>
+#include "clist.h"
+
+clist * clist_new() {
+  clist * lst;
+  
+  lst = (clist *) malloc(sizeof(clist));
+  if (!lst) return NULL;
+  
+  lst->first = lst->last = NULL;
+  lst->count = 0;
+  
+  return lst;
+}
+
+void clist_free(clist * lst) {
+  clistcell * l1, * l2;
+
+  l1 = lst->first;
+  while (l1) {
+    l2 = l1->next;
+    free(l1);
+    l1 = l2;
+  }
+
+  free(lst);
+}
+
+#ifdef NO_MACROS
+int clist_isempty(clist * lst) {
+  return ((lst->first==lst->last) && (lst->last==NULL));
+}
+
+clistiter * clist_begin(clist * lst) {
+  return lst->first;
+}
+
+clistiter * clist_end(clist * lst) {
+  return lst->last;
+}
+
+clistiter * clist_next(clistiter * iter) {
+  if (iter)
+    return iter->next;
+  else
+    return NULL;
+}
+
+clistiter * clist_previous(clistiter * iter) {
+  if (iter)
+    return iter->previous;
+  else
+    return NULL;
+}
+
+void * clist_content(clistiter * iter) {
+  if (iter)
+    return iter->data;
+  else
+    return NULL;
+}
+
+int clist_count(clist * lst) {
+  return lst->count;
+}
+
+int clist_prepend(clist * lst, void * data) {
+  return clist_insert_before(lst, lst->first, data);
+}
+
+int clist_append(clist * lst, void * data) {
+  return clist_insert_after(lst, lst->last, data);
+}
+#endif
+
+int clist_insert_before(clist * lst, clistiter * iter, void * data) {
+  clistcell * c;
+
+  c = (clistcell *) malloc(sizeof(clistcell));
+  if (!c) return -1;
+
+  c->data = data;
+  lst->count++;
+  
+  if (clist_isempty(lst)) {
+    c->previous = c->next = NULL;
+    lst->first = lst->last = c;
+    return 0;
+  }
+  
+  if (!iter) {
+    c->previous = lst->last;
+    c->previous->next = c;
+    c->next = NULL;
+    lst->last = c;
+    return 0;
+  }
+
+  c->previous = iter->previous;
+  c->next = iter;
+  c->next->previous = c;
+  if (c->previous)
+    c->previous->next = c;
+  else
+    lst->first = c;
+
+  return 0;
+}
+
+int clist_insert_after(clist * lst, clistiter * iter, void * data) {
+  clistcell * c;
+
+  c = (clistcell *) malloc(sizeof(clistcell));
+  if (!c) return -1;
+
+  c->data = data;
+  lst->count++;
+  
+  if (clist_isempty(lst)) {
+    c->previous = c->next = NULL;
+    lst->first = lst->last = c;
+    return 0;
+  }
+  
+  if (!iter) {
+    c->previous = lst->last;
+    c->previous->next = c;
+    c->next = NULL;
+    lst->last = c;
+    return 0;
+  }
+
+  c->previous = iter;
+  c->next = iter->next;
+  if (c->next)
+    c->next->previous = c;
+  else
+    lst->last = c;
+  c->previous->next = c;
+
+  return 0;
+}
+
+clistiter * clist_delete(clist * lst, clistiter * iter) {
+  clistiter * ret;
+  
+  if (!iter) return NULL;
+
+  if (iter->previous) 
+    iter->previous->next = iter->next;
+  else
+    lst->first = iter->next;
+
+  if (iter->next) {
+    iter->next->previous = iter->previous;
+    ret = iter->next;
+  }  else {
+    lst->last = iter->previous;
+    ret = NULL;
+  }
+
+  free(iter);
+  lst->count--;
+  
+  return ret;
+}
+
+
+
+void clist_foreach(clist * lst, clist_func func, void * data)
+{
+  clistiter * cur;
+
+  for(cur = clist_begin(lst) ; cur != NULL ; cur = cur->next)
+    func(cur->data, data);
+}
+
+void clist_concat(clist * dest, clist * src)
+{
+  if (src->first == NULL) {
+    /* do nothing */
+  }
+  else if (dest->last == NULL) {
+    dest->first = src->first;
+    dest->last = src->last;
+  }
+  else {
+    dest->last->next = src->first;
+    src->first->previous = dest->last;
+    dest->last = src->last;
+  }
+  
+  dest->count += src->count;
+  src->last = src->first = NULL;
+}
+
+static inline clistiter * internal_clist_nth(clist * lst, int index)
+{
+  clistiter * cur;
+
+  cur = clist_begin(lst);
+  while ((index > 0) && (cur != NULL)) {
+    cur = cur->next;
+    index --;
+  }
+
+  if (cur == NULL)
+    return NULL;
+
+  return cur;
+}
+
+void * clist_nth_data(clist * lst, int index)
+{
+  clistiter * cur;
+
+  cur = internal_clist_nth(lst, index);
+  if (cur == NULL)
+    return NULL;
+  
+  return cur->data;
+}
+
+clistiter * clist_nth(clist * lst, int index)
+{
+  return internal_clist_nth(lst, index);
+}
diff --git a/src/plugins/mailmbox/clist.h b/src/plugins/mailmbox/clist.h
new file mode 100644 (file)
index 0000000..bd97f59
--- /dev/null
@@ -0,0 +1,134 @@
+
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * clist - Implements simple generic double-linked pointer lists
+ *
+ * Copyright (c) 1999-2000, Gaël Roualland <gael.roualland@iname.com>
+ * interface changes - 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef CLIST_H
+#define CLIST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct clistcell_s {
+  void * data;
+  struct clistcell_s * previous;
+  struct clistcell_s * next;
+} clistcell;
+
+struct clist_s {
+  clistcell * first;
+  clistcell * last;
+  int count;
+};
+
+typedef struct clist_s clist;
+typedef clistcell clistiter;
+
+/* Allocate a new pointer list */
+clist *      clist_new();
+
+/* Destroys a list. Data pointed by data pointers is NOT freed. */
+void        clist_free(clist *);
+
+/* Some of the following routines can be implemented as macros to
+   be faster. If you don't want it, define NO_MACROS */
+#ifdef NO_MACROS
+
+/* Returns TRUE if list is empty */
+int         clist_isempty(clist *);
+
+/* Returns the number of elements in the list */
+int         clist_count(clist *);
+
+/* Returns an iterator to the first element of the list */
+clistiter *   clist_begin(clist *);
+
+/* Returns an iterator to the last element of the list */
+clistiter *   clist_end(clist *);
+
+/* Returns an iterator to the next element of the list */
+clistiter *   clist_next(clistiter *);
+
+/* Returns an iterator to the previous element of the list */
+clistiter *   clist_previous(clistiter *);
+
+/* Returns the data pointer of this element of the list */
+void*       clist_content(clistiter *);
+
+/* Inserts this data pointer at the beginning of the list */
+int         clist_prepend(clist *, void *);
+
+/* Inserts this data pointer at the end of the list */
+int         clist_append(clist *, void *);
+#else
+#define     clist_isempty(lst)             ((lst->first==lst->last) && (lst->last==NULL))
+#define     clist_count(lst)               (lst->count)
+#define     clist_begin(lst)               (lst->first)
+#define     clist_end(lst)                 (lst->last)
+#define     clist_next(iter)               (iter ? iter->next : NULL)
+#define     clist_previous(iter)           (iter ? iter->previous : NULL)
+#define     clist_content(iter)            (iter ? iter->data : NULL)
+#define     clist_prepend(lst, data)  (clist_insert_before(lst, lst->first, data))
+#define     clist_append(lst, data)   (clist_insert_after(lst, lst->last, data))
+#endif
+
+/* Inserts this data pointer before the element pointed by the iterator */
+int         clist_insert_before(clist *, clistiter *, void *);
+
+/* Inserts this data pointer after the element pointed by the iterator */
+int         clist_insert_after(clist *, clistiter *, void *);
+
+/* Deletes the element pointed by the iterator.
+   Returns an iterator to the next element. */
+clistiter *   clist_delete(clist *, clistiter *);
+
+typedef void (* clist_func)(void *, void *);
+
+void clist_foreach(clist * lst, clist_func func, void * data);
+
+void clist_concat(clist * dest, clist * src);
+
+void * clist_nth_data(clist * lst, int index);
+
+clistiter * clist_nth(clist * lst, int index);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/plugins/mailmbox/mailimf.c b/src/plugins/mailmbox/mailimf.c
new file mode 100644 (file)
index 0000000..03b1c19
--- /dev/null
@@ -0,0 +1,7585 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#include "mailimf.h"
+
+/*
+  RFC 2822
+
+  RFC 2821 ... 
+   A message-originating SMTP system SHOULD NOT send a message that
+   already contains a Return-path header.  SMTP servers performing a
+   relay function MUST NOT inspect the message data, and especially not
+   to the extent needed to determine if Return-path headers are present.
+   SMTP servers making final delivery MAY remove Return-path headers
+   before adding their own.
+*/
+
+#include <ctype.h>
+#include <mmapstring.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+
+
+
+
+
+
+static inline int is_dtext(char ch);
+
+static int mailimf_quoted_pair_parse(const char * message, size_t length,
+                                    size_t * index, char * result);
+
+static int mailimf_ccontent_parse(const char * message, size_t length,
+                                 size_t * index);
+
+static int
+mailimf_comment_fws_ccontent_parse(const char * message, size_t length,
+                                  size_t * index);
+
+static inline int mailimf_comment_parse(const char * message, size_t length,
+                                size_t * index);
+
+static int mailimf_qcontent_parse(const char * message, size_t length,
+                                 size_t * index, char * ch);
+
+static int mailimf_phrase_parse(const char * message, size_t length,
+                               size_t * index, char ** result);
+
+static int mailimf_unstructured_parse(const char * message, size_t length,
+                                     size_t * index, char ** result);
+
+static int mailimf_ignore_unstructured_parse(const char * message, size_t length,
+                                            size_t * index);
+
+static int mailimf_day_of_week_parse(const char * message, size_t length,
+                                    size_t * index, int * result);
+
+static int mailimf_day_name_parse(const char * message, size_t length,
+                                 size_t * index, int * result);
+
+static int mailimf_date_parse(const char * message, size_t length,
+                             size_t * index,
+                             int * pday, int * pmonth, int * pyear);
+
+static int mailimf_year_parse(const char * message, size_t length,
+                             size_t * index, int * result);
+
+static int mailimf_month_parse(const char * message, size_t length,
+                              size_t * index, int * result);
+
+static int mailimf_month_name_parse(const char * message, size_t length,
+                                   size_t * index, int * result);
+
+static int mailimf_day_parse(const char * message, size_t length,
+                                 size_t * index, int * result);
+
+static int mailimf_time_parse(const char * message, size_t length,
+                             size_t * index, 
+                             int * phour, int * pmin,
+                             int * psec,
+                             int * zone);
+static int mailimf_time_of_day_parse(const char * message, size_t length,
+                                    size_t * index,
+                                    int * phour, int * pmin,
+                                    int * psec);
+
+static int mailimf_hour_parse(const char * message, size_t length,
+                             size_t * index, int * result);
+
+static int mailimf_minute_parse(const char * message, size_t length,
+                               size_t * index, int * result);
+
+static int mailimf_second_parse(const char * message, size_t length,
+                               size_t * index, int * result);
+
+static int mailimf_zone_parse(const char * message, size_t length,
+                             size_t * index, int * result);
+
+static int mailimf_name_addr_parse(const char * message, size_t length,
+                                  size_t * index,
+                                  char ** pdisplay_name,
+                                  char ** pangle_addr);
+
+static int mailimf_angle_addr_parse(const char * message, size_t length,
+                                   size_t * index, char ** result);
+
+static int mailimf_group_parse(const char * message, size_t length,
+                              size_t * index,
+                              struct mailimf_group ** result);
+
+static int mailimf_display_name_parse(const char * message, size_t length,
+                                     size_t * index, char ** result);
+
+static int mailimf_addr_spec_parse(const char * message, size_t length,
+                                  size_t * index,
+                                  char ** address);
+
+#if 0
+static int mailimf_local_part_parse(const char * message, size_t length,
+                                   size_t * index,
+                                   char ** result);
+
+static int mailimf_domain_parse(const char * message, size_t length,
+                               size_t * index,
+                               char ** result);
+#endif
+
+#if 0
+static int mailimf_domain_literal_parse(const char * message, size_t length,
+                                       size_t * index, char ** result);
+#endif
+
+#if 0
+static int mailimf_dcontent_parse(const char * message, size_t length,
+                                 size_t * index, char * result);
+#endif
+
+static int
+mailimf_orig_date_parse(const char * message, size_t length,
+                       size_t * index, struct mailimf_orig_date ** result);
+
+static int
+mailimf_from_parse(const char * message, size_t length,
+                  size_t * index, struct mailimf_from ** result);
+
+static int
+mailimf_sender_parse(const char * message, size_t length,
+                    size_t * index, struct mailimf_sender ** result);
+
+static int
+mailimf_reply_to_parse(const char * message, size_t length,
+                      size_t * index, struct mailimf_reply_to ** result);
+
+static int
+mailimf_to_parse(const char * message, size_t length,
+                size_t * index, struct mailimf_to ** result);
+
+static int
+mailimf_cc_parse(const char * message, size_t length,
+                size_t * index, struct mailimf_cc ** result);
+
+static int
+mailimf_bcc_parse(const char * message, size_t length,
+                 size_t * index, struct mailimf_bcc ** result);
+
+static int mailimf_message_id_parse(const char * message, size_t length,
+                                   size_t * index,
+                                   struct mailimf_message_id ** result);
+
+static int
+mailimf_in_reply_to_parse(const char * message, size_t length,
+                         size_t * index,
+                         struct mailimf_in_reply_to ** result);
+
+#if 0
+static int mailimf_references_parse(const char * message, size_t length,
+                                   size_t * index,
+                                   struct mailimf_references **
+                                   result);
+#endif
+
+static int mailimf_unstrict_msg_id_parse(const char * message, size_t length,
+                                        size_t * index,
+                                        char ** result);
+
+#if 0
+static int mailimf_id_left_parse(const char * message, size_t length,
+                                size_t * index, char ** result);
+
+static int mailimf_id_right_parse(const char * message, size_t length,
+                                 size_t * index, char ** result);
+#endif
+
+#if 0
+static int mailimf_no_fold_quote_parse(const char * message, size_t length,
+                                      size_t * index, char ** result);
+
+static int mailimf_no_fold_literal_parse(const char * message, size_t length,
+                                        size_t * index, char ** result);
+#endif
+
+static int mailimf_subject_parse(const char * message, size_t length,
+                                size_t * index,
+                                struct mailimf_subject ** result);
+
+static int mailimf_comments_parse(const char * message, size_t length,
+                                 size_t * index,
+                                 struct mailimf_comments ** result);
+
+static int mailimf_keywords_parse(const char * message, size_t length,
+                                 size_t * index,
+                                 struct mailimf_keywords ** result);
+
+static int
+mailimf_resent_date_parse(const char * message, size_t length,
+                         size_t * index, struct mailimf_orig_date ** result);
+
+static int
+mailimf_resent_from_parse(const char * message, size_t length,
+                         size_t * index, struct mailimf_from ** result);
+
+static int
+mailimf_resent_sender_parse(const char * message, size_t length,
+                           size_t * index, struct mailimf_sender ** result);
+
+static int
+mailimf_resent_to_parse(const char * message, size_t length,
+                       size_t * index, struct mailimf_to ** result);
+
+static int
+mailimf_resent_cc_parse(const char * message, size_t length,
+                       size_t * index, struct mailimf_cc ** result);
+
+static int
+mailimf_resent_bcc_parse(const char * message, size_t length,
+                        size_t * index, struct mailimf_bcc ** result);
+
+static int
+mailimf_resent_msg_id_parse(const char * message, size_t length,
+                           size_t * index,
+                           struct mailimf_message_id ** result);
+
+static int mailimf_return_parse(const char * message, size_t length,
+                               size_t * index,
+                               struct mailimf_return ** result);
+
+static int
+mailimf_path_parse(const char * message, size_t length,
+                  size_t * index, struct mailimf_path ** result);
+
+static int
+mailimf_optional_field_parse(const char * message, size_t length,
+                            size_t * index,
+                            struct mailimf_optional_field ** result);
+
+static int mailimf_field_name_parse(const char * message, size_t length,
+                                   size_t * index, char ** result);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/* *************************************************************** */
+
+static inline int is_digit(char ch)
+{
+  return (ch >= '0') && (ch <= '9');
+}
+
+static int mailimf_digit_parse(const char * message, size_t length,
+                              size_t * index, int * result)
+{
+  size_t cur_token;
+
+  cur_token = * index;
+  
+  if (cur_token >= length)
+    return MAILIMF_ERROR_PARSE;
+
+  if (is_digit(message[cur_token])) {
+    * result = message[cur_token] - '0';
+    cur_token ++;
+    * index = cur_token;
+    return MAILIMF_NO_ERROR;
+  }
+  else
+    return MAILIMF_ERROR_PARSE;
+}
+
+int
+mailimf_number_parse(const char * message, size_t length,
+                    size_t * index, uint32_t * result)
+{
+  size_t cur_token;
+  int digit;
+  uint32_t number;
+  int parsed;
+  int r;
+
+  cur_token = * index;
+  parsed = FALSE;
+
+  number = 0;
+  while (1) {
+    r = mailimf_digit_parse(message, length, &cur_token, &digit);
+    if (r != MAILIMF_NO_ERROR) {
+      if (r == MAILIMF_ERROR_PARSE)
+       break;
+      else
+       return r;
+    }
+    number *= 10;
+    number += digit;
+    parsed = TRUE;
+  }
+
+  if (!parsed)
+    return MAILIMF_ERROR_PARSE;
+
+  * result = number;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+int mailimf_char_parse(const char * message, size_t length,
+                      size_t * index, char token)
+{
+  size_t cur_token;
+
+  cur_token = * index;
+
+  if (cur_token >= length)
+    return MAILIMF_ERROR_PARSE;
+
+  if (message[cur_token] == token) {
+    cur_token ++;
+    * index = cur_token;
+    return MAILIMF_NO_ERROR;
+  }
+  else
+    return MAILIMF_ERROR_PARSE;
+}
+
+int mailimf_unstrict_char_parse(const char * message, size_t length,
+                               size_t * index, char token)
+{
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_char_parse(message, length, &cur_token, token);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+  
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+int
+mailimf_token_case_insensitive_len_parse(const char * message, size_t length,
+                                        size_t * index, char * token,
+                                        size_t token_length)
+{
+  size_t cur_token;
+
+  cur_token = * index;
+
+  if (cur_token + token_length - 1 >= length)
+    return MAILIMF_ERROR_PARSE;
+
+  if (strncasecmp(message + cur_token, token, token_length) == 0) {
+    cur_token += token_length;
+    * index = cur_token;
+    return MAILIMF_NO_ERROR;
+  }
+  else
+    return MAILIMF_ERROR_PARSE;
+}
+
+static int mailimf_oparenth_parse(const char * message, size_t length,
+                                 size_t * index)
+{
+  return mailimf_char_parse(message, length, index, '(');
+}
+
+static int mailimf_cparenth_parse(const char * message, size_t length,
+                                 size_t * index)
+{
+  return mailimf_char_parse(message, length, index, ')');
+}
+
+static int mailimf_comma_parse(const char * message, size_t length,
+                              size_t * index)
+{
+  return mailimf_unstrict_char_parse(message, length, index, ',');
+}
+
+static int mailimf_dquote_parse(const char * message, size_t length,
+                               size_t * index)
+{
+  return mailimf_char_parse(message, length, index, '\"');
+}
+
+static int mailimf_colon_parse(const char * message, size_t length,
+                              size_t * index)
+{
+  return mailimf_unstrict_char_parse(message, length, index, ':');
+}
+
+static int mailimf_semi_colon_parse(const char * message, size_t length,
+                                   size_t * index)
+{
+  return mailimf_unstrict_char_parse(message, length, index, ';');
+}
+
+static int mailimf_plus_parse(const char * message, size_t length,
+                             size_t * index)
+{
+  return mailimf_unstrict_char_parse(message, length, index, '+');
+}
+
+static int mailimf_minus_parse(const char * message, size_t length,
+                              size_t * index)
+{
+  return mailimf_unstrict_char_parse(message, length, index, '-');
+}
+
+static int mailimf_lower_parse(const char * message, size_t length,
+                              size_t * index)
+{
+  return mailimf_unstrict_char_parse(message, length, index, '<');
+}
+
+static int mailimf_greater_parse(const char * message, size_t length,
+                                     size_t * index)
+{
+  return mailimf_unstrict_char_parse(message, length, index, '>');
+}
+
+#if 0
+static int mailimf_obracket_parse(const char * message, size_t length,
+                                      size_t * index)
+{
+  return mailimf_unstrict_char_parse(message, length, index, '[');
+}
+
+static int mailimf_cbracket_parse(const char * message, size_t length,
+                                      size_t * index)
+{
+  return mailimf_unstrict_char_parse(message, length, index, ']');
+}
+#endif
+
+static int mailimf_at_sign_parse(const char * message, size_t length,
+                                     size_t * index)
+{
+  return mailimf_unstrict_char_parse(message, length, index, '@');
+}
+
+static int mailimf_point_parse(const char * message, size_t length,
+                                     size_t * index)
+{
+  return mailimf_unstrict_char_parse(message, length, index, '.');
+}
+
+int
+mailimf_custom_string_parse(const char * message, size_t length,
+                           size_t * index, char ** result,
+                           int (* is_custom_char)(char))
+{
+  size_t begin;
+  size_t end;
+  char * gstr;
+
+  begin = * index;
+
+  end = begin;
+
+  if (end >= length)
+    return MAILIMF_ERROR_PARSE;
+
+  while (is_custom_char(message[end])) {
+    end ++;
+    if (end >= length)
+      break;
+  }
+
+  if (end != begin) {
+    /*
+    gstr = strndup(message + begin, end - begin);
+    */
+    gstr = malloc(end - begin + 1);
+    if (gstr == NULL)
+      return MAILIMF_ERROR_MEMORY;
+    strncpy(gstr, message + begin, end - begin);
+    gstr[end - begin] = '\0';
+
+    * index = end;
+    * result = gstr;
+    return MAILIMF_NO_ERROR;
+  }
+  else
+    return MAILIMF_ERROR_PARSE;
+}
+
+
+
+
+
+
+
+typedef int mailimf_struct_parser(const char * message, size_t length,
+                                 size_t * index, void * result);
+
+typedef int mailimf_struct_destructor(void * result);
+
+
+static int
+mailimf_struct_multiple_parse(const char * message, size_t length,
+                             size_t * index, clist ** result,
+                             mailimf_struct_parser * parser,
+                             mailimf_struct_destructor * destructor)
+{
+  clist * struct_list;
+  size_t cur_token;
+  void * value;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = parser(message, length, &cur_token, &value);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  struct_list = clist_new();
+  if (struct_list == NULL) {
+    destructor(value);
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+
+  r = clist_append(struct_list, value);
+  if (r < 0) {
+    destructor(value);
+    res = MAILIMF_ERROR_MEMORY;
+    goto free;
+  }
+
+  while (1) {
+    r = parser(message, length, &cur_token, &value);
+    if (r != MAILIMF_NO_ERROR) {
+      if (r == MAILIMF_ERROR_PARSE)
+       break;
+      else {
+       res = r;
+       goto free;
+      }
+    }
+    r = clist_append(struct_list, value);
+    if (r < 0) {
+      (* destructor)(value);
+      res = MAILIMF_ERROR_MEMORY;
+      goto free;
+    }
+  }
+
+  * result = struct_list;
+  * index = cur_token;
+  
+  return MAILIMF_NO_ERROR;
+
+ free:
+  clist_foreach(struct_list, (clist_func) destructor, NULL);
+  clist_free(struct_list);
+ err:
+  return res;
+}
+
+
+
+static int
+mailimf_struct_list_parse(const char * message, size_t length,
+                         size_t * index, clist ** result,
+                         char symbol,
+                         mailimf_struct_parser * parser,
+                         mailimf_struct_destructor * destructor)
+{
+  clist * struct_list;
+  size_t cur_token;
+  void * value;
+  size_t final_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = parser(message, length, &cur_token, &value);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  struct_list = clist_new();
+  if (struct_list == NULL) {
+    destructor(value);
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+
+  r = clist_append(struct_list, value);
+  if (r < 0) {
+    destructor(value);
+    res = MAILIMF_ERROR_MEMORY;
+    goto free;
+  }
+
+  final_token = cur_token;
+
+  while (1) {
+    r = mailimf_unstrict_char_parse(message, length, &cur_token, symbol);
+    if (r != MAILIMF_NO_ERROR) {
+      if (r == MAILIMF_ERROR_PARSE)
+       break;
+      else {
+       res = r;
+       goto free;
+      }
+    }
+
+    r = parser(message, length, &cur_token, &value);
+    if (r != MAILIMF_NO_ERROR) {
+      if (r == MAILIMF_ERROR_PARSE)
+       break;
+      else {
+       res = r;
+       goto free;
+      }
+    }
+
+    r = clist_append(struct_list, value);
+    if (r < 0) {
+      destructor(value);
+      res = MAILIMF_ERROR_MEMORY;
+      goto free;
+    }
+
+    final_token = cur_token;
+  }
+  
+  * result = struct_list;
+  * index = final_token;
+  
+  return MAILIMF_NO_ERROR;
+  
+ free:
+  clist_foreach(struct_list, (clist_func) destructor, NULL);
+  clist_free(struct_list);
+ err:
+  return res;
+}
+
+static inline int mailimf_wsp_parse(const char * message, size_t length,
+                                   size_t * index)
+{
+  size_t cur_token;
+
+  cur_token = * index;
+
+  if (cur_token >= length)
+    return MAILIMF_ERROR_PARSE;
+
+  if ((message[cur_token] != ' ') && (message[cur_token] != '\t'))
+    return MAILIMF_ERROR_PARSE;
+
+  cur_token ++;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+int mailimf_crlf_parse(const char * message, size_t length, size_t * index)
+{
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_char_parse(message, length, &cur_token, '\r');
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_char_parse(message, length, &cur_token, '\n');
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * index = cur_token;
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_unstrict_crlf_parse(const char * message,
+                                      size_t length, size_t * index)
+{
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+  mailimf_cfws_parse(message, length, &cur_token);
+
+  r = mailimf_char_parse(message, length, &cur_token, '\r');
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_char_parse(message, length, &cur_token, '\n');
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * index = cur_token;
+  return MAILIMF_NO_ERROR;
+}
+
+/* ************************************************************************ */
+
+
+
+/* RFC 2822 grammar */
+
+/*
+NO-WS-CTL       =       %d1-8 /         ; US-ASCII control characters
+                        %d11 /          ;  that do not include the
+                        %d12 /          ;  carriage return, line feed,
+                        %d14-31 /       ;  and white space characters
+                        %d127
+*/
+
+static inline int is_no_ws_ctl(char ch)
+{
+  if ((ch == 9) || (ch == 10) || (ch == 13))
+    return FALSE;
+
+  if (ch == 127)
+     return TRUE;
+
+  return (ch >= 1) && (ch <= 31);
+}
+
+/*
+text            =       %d1-9 /         ; Characters excluding CR and LF
+                        %d11 /
+                        %d12 /
+                        %d14-127 /
+                        obs-text
+*/
+
+/*
+specials        =       "(" / ")" /     ; Special characters used in
+                        "<" / ">" /     ;  other parts of the syntax
+                        "[" / "]" /
+                        ":" / ";" /
+                        "@" / "\" /
+                        "," / "." /
+                        DQUOTE
+*/
+
+/*
+quoted-pair     =       ("\" text) / obs-qp
+*/
+
+static inline int mailimf_quoted_pair_parse(const char * message, size_t length,
+                                           size_t * index, char * result)
+{
+  size_t cur_token;
+
+  cur_token = * index;
+  
+  if (cur_token + 1 >= length)
+    return MAILIMF_ERROR_PARSE;
+
+  if (message[cur_token] != '\\')
+    return MAILIMF_ERROR_PARSE;
+
+  cur_token ++;
+  * result = message[cur_token];
+  cur_token ++;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+FWS             =       ([*WSP CRLF] 1*WSP) /   ; Folding white space
+                        obs-FWS
+*/
+
+int mailimf_fws_parse(const char * message, size_t length, size_t * index)
+{
+  size_t cur_token;
+  size_t final_token;
+  int fws_1;
+  int fws_2;
+  int fws_3;
+  int r;
+  
+  cur_token = * index;
+
+  fws_1 = FALSE;
+  while (1) {
+    r = mailimf_wsp_parse(message, length, &cur_token);
+    if (r != MAILIMF_NO_ERROR) {
+      if (r == MAILIMF_ERROR_PARSE)
+       break;
+      else
+       return r;
+    }
+    fws_1 = TRUE;
+  }
+  final_token = cur_token;
+
+  r = mailimf_crlf_parse(message, length, &cur_token);
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    fws_2 = TRUE;
+    break;
+  case MAILIMF_ERROR_PARSE:
+    fws_2 = FALSE;
+    break;
+  default:
+      return r;
+  }
+  
+  fws_3 = FALSE;
+  if (fws_2) {
+    while (1) {
+      r = mailimf_wsp_parse(message, length, &cur_token);
+      if (r != MAILIMF_NO_ERROR) {
+       if (r == MAILIMF_ERROR_PARSE)
+         break;
+       else
+         return r;
+      }
+      fws_3 = TRUE;
+    }
+  }
+
+  if ((!fws_1) && (!fws_3))
+    return MAILIMF_ERROR_PARSE;
+
+  if (!fws_3)
+    cur_token = final_token;
+
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+/*
+ctext           =       NO-WS-CTL /     ; Non white space controls
+
+                        %d33-39 /       ; The rest of the US-ASCII
+                        %d42-91 /       ;  characters not including "(",
+                        %d93-126        ;  ")", or "\"
+*/
+
+static inline int is_ctext(char ch)
+{
+  unsigned char uch = (unsigned char) ch;
+
+  if (is_no_ws_ctl(ch))
+    return TRUE;
+
+  if (uch < 33)
+    return FALSE;
+
+  if ((uch == 40) || (uch == 41))
+    return FALSE;
+  
+  if (uch == 92)
+    return FALSE;
+
+  if (uch == 127)
+    return FALSE;
+
+  return TRUE;
+}
+
+/*
+ccontent        =       ctext / quoted-pair / comment
+*/
+
+static inline int mailimf_ccontent_parse(const char * message, size_t length,
+                                        size_t * index)
+{
+  size_t cur_token;
+  char ch;
+  int r;
+  
+  cur_token = * index;
+
+  if (cur_token >= length)
+    return MAILIMF_ERROR_PARSE;
+
+  if (is_ctext(message[cur_token])) {
+    cur_token ++;
+  }
+  else {
+    r = mailimf_quoted_pair_parse(message, length, &cur_token, &ch);
+    
+    if (r == MAILIMF_ERROR_PARSE)
+      r = mailimf_comment_parse(message, length, &cur_token);
+    
+    if (r == MAILIMF_ERROR_PARSE)
+      return r;
+  }
+
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+[FWS] ccontent
+*/
+
+static inline int
+mailimf_comment_fws_ccontent_parse(const char * message, size_t length,
+                                  size_t * index)
+{
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_fws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_ccontent_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+comment         =       "(" *([FWS] ccontent) [FWS] ")"
+*/
+
+static inline int mailimf_comment_parse(const char * message, size_t length,
+                                size_t * index)
+{
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_oparenth_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  while (1) {
+    r = mailimf_comment_fws_ccontent_parse(message, length, &cur_token);
+    if (r != MAILIMF_NO_ERROR) {
+      if (r == MAILIMF_ERROR_PARSE)
+       break;
+      else
+       return r;
+    }
+  }
+
+  r = mailimf_fws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_cparenth_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+[FWS] comment
+*/
+
+static inline int mailimf_cfws_fws_comment_parse(const char * message, size_t length,
+                                                size_t * index)
+{
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_fws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_comment_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+CFWS            =       *([FWS] comment) (([FWS] comment) / FWS)
+*/
+
+int mailimf_cfws_parse(const char * message, size_t length,
+                      size_t * index)
+{
+  size_t cur_token;
+  int has_comment;
+  int r;
+
+  cur_token = * index;
+
+  has_comment = FALSE;
+  while (1) {
+    r = mailimf_cfws_fws_comment_parse(message, length, &cur_token);
+    if (r != MAILIMF_NO_ERROR) {
+      if (r == MAILIMF_ERROR_PARSE)
+       break;
+      else
+       return r;
+    }
+    has_comment = TRUE;
+  }
+
+  if (!has_comment) {
+    r = mailimf_fws_parse(message, length, &cur_token);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+atext           =       ALPHA / DIGIT / ; Any character except controls,
+                        "!" / "#" /     ;  SP, and specials.
+                        "$" / "%" /     ;  Used for atoms
+                        "&" / "'" /
+                        "*" / "+" /
+                        "-" / "/" /
+                        "=" / "?" /
+                        "^" / "_" /
+                        "`" / "{" /
+                        "|" / "}" /
+                        "~"
+*/
+
+static inline int is_atext(char ch)
+{
+  switch (ch) {
+  case ' ':
+  case '\t':
+  case '\n':
+  case '\r':
+#if 0
+  case '(':
+  case ')':
+#endif
+  case '<':
+  case '>':
+#if 0
+  case '@':
+#endif
+  case ',':
+  case '"':
+  case ':':
+  case ';':
+    return FALSE;
+  default:
+    return TRUE;
+  }
+}
+
+/*
+atom            =       [CFWS] 1*atext [CFWS]
+*/
+
+int mailimf_atom_parse(const char * message, size_t length,
+                      size_t * index, char ** result)
+{
+  size_t cur_token;
+  int r;
+  int res;
+  char * atom;
+  size_t end;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+  
+  end = cur_token;
+  if (end >= length) {
+    res = MAILIMF_ERROR_PARSE;
+    goto err;
+  }
+
+  while (is_atext(message[end])) {
+    end ++;
+    if (end >= length)
+      break;
+  }
+  if (end == cur_token) {
+    res = MAILIMF_ERROR_PARSE;
+    goto err;
+  }
+
+  atom = malloc(end - cur_token + 1);
+  if (atom == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+  strncpy(atom, message + cur_token, end - cur_token);
+  atom[end - cur_token] = '\0';
+
+  cur_token = end;
+
+  * index = cur_token;
+  * result = atom;
+
+  return MAILIMF_NO_ERROR;
+
+ err:
+  return res;
+}
+
+int mailimf_fws_atom_parse(const char * message, size_t length,
+                          size_t * index, char ** result)
+{
+  size_t cur_token;
+  int r;
+  int res;
+  char * atom;
+  size_t end;
+
+  cur_token = * index;
+
+  r = mailimf_fws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+
+  end = cur_token;
+  if (end >= length) {
+    res = MAILIMF_ERROR_PARSE;
+    goto err;
+  }
+
+  while (is_atext(message[end])) {
+    end ++;
+    if (end >= length)
+      break;
+  }
+  if (end == cur_token) {
+    res = MAILIMF_ERROR_PARSE;
+    goto err;
+  }
+
+  atom = malloc(end - cur_token + 1);
+  if (atom == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+  strncpy(atom, message + cur_token, end - cur_token);
+  atom[end - cur_token] = '\0';
+
+  cur_token = end;
+
+  * index = cur_token;
+  * result = atom;
+
+  return MAILIMF_NO_ERROR;
+
+ err:
+  return res;
+}
+
+/*
+dot-atom        =       [CFWS] dot-atom-text [CFWS]
+*/
+
+#if 0
+static int mailimf_dot_atom_parse(const char * message, size_t length,
+                                 size_t * index, char ** result)
+{
+  return mailimf_atom_parse(message, length, index, result);
+}
+#endif
+
+/*
+dot-atom-text   =       1*atext *("." 1*atext)
+*/
+
+#if 0
+static int
+mailimf_dot_atom_text_parse(const char * message, size_t length,
+                           size_t * index, char ** result)
+{
+  return mailimf_atom_parse(message, length, index, result);
+}
+#endif
+
+/*
+qtext           =       NO-WS-CTL /     ; Non white space controls
+
+                        %d33 /          ; The rest of the US-ASCII
+                        %d35-91 /       ;  characters not including "\"
+                        %d93-126        ;  or the quote character
+*/
+
+static inline int is_qtext(char ch)
+{
+  unsigned char uch = (unsigned char) ch;
+
+  if (is_no_ws_ctl(ch))
+    return TRUE;
+
+  if (uch < 33)
+    return FALSE;
+
+  if (uch == 34)
+    return FALSE;
+
+  if (uch == 92)
+    return FALSE;
+
+  if (uch == 127)
+    return FALSE;
+
+  return TRUE;
+}
+
+/*
+qcontent        =       qtext / quoted-pair
+*/
+
+static int mailimf_qcontent_parse(const char * message, size_t length,
+                                 size_t * index, char * result)
+{
+  size_t cur_token;
+  char ch;
+  int r;
+
+  cur_token = * index;
+
+  if (cur_token >= length)
+    return MAILIMF_ERROR_PARSE;
+
+  if (is_qtext(message[cur_token])) {
+    ch = message[cur_token];
+    cur_token ++;
+  }
+  else {
+    r = mailimf_quoted_pair_parse(message, length, &cur_token, &ch);
+    
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+  
+  * result = ch;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+quoted-string   =       [CFWS]
+                        DQUOTE *([FWS] qcontent) [FWS] DQUOTE
+                        [CFWS]
+*/
+
+int mailimf_quoted_string_parse(const char * message, size_t length,
+                               size_t * index, char ** result)
+{
+  size_t cur_token;
+  MMAPString * gstr;
+  char ch;
+  char * str;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_dquote_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  gstr = mmap_string_new("");
+  if (gstr == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+
+#if 0
+  if (mmap_string_append_c(gstr, '\"') == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_gstr;
+  }
+#endif
+
+  while (1) {
+    r = mailimf_fws_parse(message, length, &cur_token);
+    if (r == MAILIMF_NO_ERROR) {
+      if (mmap_string_append_c(gstr, ' ') == NULL) {
+       res = MAILIMF_ERROR_MEMORY;
+       goto free_gstr;
+      }
+    }
+    else if (r != MAILIMF_ERROR_PARSE) {
+      res = r;
+      goto free_gstr;
+    }
+
+    r = mailimf_qcontent_parse(message, length, &cur_token, &ch);
+    if (r == MAILIMF_NO_ERROR) {
+      if (mmap_string_append_c(gstr, ch) == NULL) {
+       res = MAILIMF_ERROR_MEMORY;
+       goto free_gstr;
+      }
+    }
+    else if (r == MAILIMF_ERROR_PARSE)
+      break;
+    else {
+      res = r;
+      goto free_gstr;
+    }
+  }
+
+  r = mailimf_dquote_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_gstr;
+  }
+
+#if 0
+  if (mmap_string_append_c(gstr, '\"') == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_gstr;
+  }
+#endif
+
+  str = strdup(gstr->str);
+  if (str == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_gstr;
+  }
+  mmap_string_free(gstr);
+
+  * index = cur_token;
+  * result = str;
+
+  return MAILIMF_NO_ERROR;
+
+ free_gstr:
+  mmap_string_free(gstr);
+ err:
+  return res;
+}
+
+int mailimf_fws_quoted_string_parse(const char * message, size_t length,
+                                   size_t * index, char ** result)
+{
+  size_t cur_token;
+  MMAPString * gstr;
+  char ch;
+  char * str;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_fws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_dquote_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  gstr = mmap_string_new("");
+  if (gstr == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+
+#if 0
+  if (mmap_string_append_c(gstr, '\"') == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_gstr;
+  }
+#endif
+
+  while (1) {
+    r = mailimf_fws_parse(message, length, &cur_token);
+    if (r == MAILIMF_NO_ERROR) {
+      if (mmap_string_append_c(gstr, ' ') == NULL) {
+       res = MAILIMF_ERROR_MEMORY;
+       goto free_gstr;
+      }
+    }
+    else if (r != MAILIMF_ERROR_PARSE) {
+      res = r;
+      goto free_gstr;
+    }
+
+    r = mailimf_qcontent_parse(message, length, &cur_token, &ch);
+    if (r == MAILIMF_NO_ERROR) {
+      if (mmap_string_append_c(gstr, ch) == NULL) {
+       res = MAILIMF_ERROR_MEMORY;
+       goto free_gstr;
+      }
+    }
+    else if (r == MAILIMF_ERROR_PARSE)
+      break;
+    else {
+      res = r;
+      goto free_gstr;
+    }
+  }
+
+  r = mailimf_dquote_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_gstr;
+  }
+
+#if 0
+  if (mmap_string_append_c(gstr, '\"') == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_gstr;
+  }
+#endif
+
+  str = strdup(gstr->str);
+  if (str == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_gstr;
+  }
+  mmap_string_free(gstr);
+
+  * index = cur_token;
+  * result = str;
+
+  return MAILIMF_NO_ERROR;
+
+ free_gstr:
+  mmap_string_free(gstr);
+ err:
+  return res;
+}
+
+/*
+word            =       atom / quoted-string
+*/
+
+int mailimf_word_parse(const char * message, size_t length,
+                      size_t * index, char ** result)
+{
+  size_t cur_token;
+  char * word;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_atom_parse(message, length, &cur_token, &word);
+
+  if (r == MAILIMF_ERROR_PARSE)
+    r = mailimf_quoted_string_parse(message, length, &cur_token, &word);
+
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * result = word;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+int mailimf_fws_word_parse(const char * message, size_t length,
+                          size_t * index, char ** result)
+{
+  size_t cur_token;
+  char * word;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_fws_atom_parse(message, length, &cur_token, &word);
+
+  if (r == MAILIMF_ERROR_PARSE)
+    r = mailimf_fws_quoted_string_parse(message, length, &cur_token, &word);
+
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * result = word;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+phrase          =       1*word / obs-phrase
+*/
+
+static int mailimf_phrase_parse(const char * message, size_t length,
+                               size_t * index, char ** result)
+{
+  MMAPString * gphrase;
+  char * word;
+  int first;
+  size_t cur_token;
+  int r;
+  int res;
+  char * str;
+
+  cur_token = * index;
+
+  gphrase = mmap_string_new("");
+  if (gphrase == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+
+  first = TRUE;
+
+  while (1) {
+    r = mailimf_fws_word_parse(message, length, &cur_token, &word);
+    if (r == MAILIMF_NO_ERROR) {
+      if (!first) {
+       if (mmap_string_append_c(gphrase, ' ') == NULL) {
+         mailimf_word_free(word);
+         res = MAILIMF_ERROR_MEMORY;
+         goto free;
+       }
+      }
+      if (mmap_string_append(gphrase, word) == NULL) {
+       mailimf_word_free(word);
+       res = MAILIMF_ERROR_MEMORY;
+       goto free;
+      }
+      mailimf_word_free(word);
+      first = FALSE;
+    }
+    else if (r == MAILIMF_ERROR_PARSE)
+      break;
+    else {
+      res = r;
+      goto free;
+    }
+  }
+
+  if (first) {
+    res = MAILIMF_ERROR_PARSE;
+    goto free;
+  }
+
+  str = strdup(gphrase->str);
+  if (str == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free;
+  }
+  mmap_string_free(gphrase);
+
+  * result = str;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free:
+  mmap_string_free(gphrase);
+ err:
+  return res;
+}
+
+/*
+utext           =       NO-WS-CTL /     ; Non white space controls
+                        %d33-126 /      ; The rest of US-ASCII
+                        obs-utext
+
+added : WSP
+*/
+
+enum {
+  UNSTRUCTURED_START,
+  UNSTRUCTURED_CR,
+  UNSTRUCTURED_LF,
+  UNSTRUCTURED_WSP,
+  UNSTRUCTURED_OUT
+};
+
+static int mailimf_unstructured_parse(const char * message, size_t length,
+                                     size_t * index, char ** result)
+{
+  size_t cur_token;
+  int state;
+  size_t begin;
+  size_t terminal;
+  char * str;
+
+  cur_token = * index;
+
+
+  while (1) {
+    int r;
+
+    r = mailimf_wsp_parse(message, length, &cur_token);
+    if (r == MAILIMF_NO_ERROR) {
+      /* do nothing */
+    }
+    else if (r == MAILIMF_ERROR_PARSE)
+      break;
+    else {
+      return r;
+    }
+  }
+
+  state = UNSTRUCTURED_START;
+  begin = cur_token;
+  terminal = cur_token;
+
+  while (state != UNSTRUCTURED_OUT) {
+
+    switch(state) {
+    case UNSTRUCTURED_START:
+      if (cur_token >= length)
+       return MAILIMF_ERROR_PARSE;
+
+      terminal = cur_token;
+      switch(message[cur_token]) {
+      case '\r':
+       state = UNSTRUCTURED_CR;
+       break;
+      case '\n':
+       state = UNSTRUCTURED_LF;
+       break;
+      default:
+       state = UNSTRUCTURED_START;
+       break;
+      }
+      break;
+    case UNSTRUCTURED_CR:
+      if (cur_token >= length)
+       return MAILIMF_ERROR_PARSE;
+
+      switch(message[cur_token]) {
+      case '\n':
+       state = UNSTRUCTURED_LF;
+       break;
+      default:
+       state = UNSTRUCTURED_START;
+       break;
+      }
+      break;
+
+    case UNSTRUCTURED_LF:
+      if (cur_token >= length) {
+       state = UNSTRUCTURED_OUT;
+       break;
+      }
+
+      switch(message[cur_token]) {
+      case '\t':
+      case ' ':
+       state = UNSTRUCTURED_WSP;
+       break;
+      default:
+       state = UNSTRUCTURED_OUT;
+       break;
+      }
+      break;
+    case UNSTRUCTURED_WSP:
+      if (cur_token >= length)
+       return MAILIMF_ERROR_PARSE;
+
+      switch(message[cur_token]) {
+      case '\r':
+       state = UNSTRUCTURED_CR;
+       break;
+      case '\n':
+       state = UNSTRUCTURED_LF;
+       break;
+      default:
+       state = UNSTRUCTURED_START;
+       break;
+      }
+      break;
+    }
+
+    cur_token ++;
+  }
+
+  str = malloc(terminal - begin + 1);
+  if (str == NULL)
+    return MAILIMF_ERROR_MEMORY;
+  strncpy(str, message + begin,  terminal - begin);
+  str[terminal - begin] = '\0';
+
+  * index = terminal;
+  * result = str;
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_ignore_unstructured_parse(const char * message, size_t length,
+                                            size_t * index)
+{
+  size_t cur_token;
+  int state;
+  size_t terminal;
+
+  cur_token = * index;
+
+  state = UNSTRUCTURED_START;
+  terminal = cur_token;
+
+  while (state != UNSTRUCTURED_OUT) {
+
+    switch(state) {
+    case UNSTRUCTURED_START:
+      if (cur_token >= length)
+       return MAILIMF_ERROR_PARSE;
+      terminal = cur_token;
+      switch(message[cur_token]) {
+      case '\r':
+       state = UNSTRUCTURED_CR;
+       break;
+      case '\n':
+       state = UNSTRUCTURED_LF;
+       break;
+      default:
+       state = UNSTRUCTURED_START;
+       break;
+      }
+      break;
+    case UNSTRUCTURED_CR:
+      if (cur_token >= length)
+       return MAILIMF_ERROR_PARSE;
+      switch(message[cur_token]) {
+      case '\n':
+       state = UNSTRUCTURED_LF;
+       break;
+      default:
+       state = UNSTRUCTURED_START;
+       break;
+      }
+      break;
+    case UNSTRUCTURED_LF:
+      if (cur_token >= length) {
+       state = UNSTRUCTURED_OUT;
+       break;
+      }
+      switch(message[cur_token]) {
+      case '\t':
+      case ' ':
+       state = UNSTRUCTURED_WSP;
+       break;
+      default:
+       state = UNSTRUCTURED_OUT;
+       break;
+      }
+      break;
+    case UNSTRUCTURED_WSP:
+      if (cur_token >= length)
+       return MAILIMF_ERROR_PARSE;
+      switch(message[cur_token]) {
+      case '\r':
+       state = UNSTRUCTURED_CR;
+       break;
+      case '\n':
+       state = UNSTRUCTURED_LF;
+       break;
+      default:
+       state = UNSTRUCTURED_START;
+       break;
+      }
+      break;
+    }
+
+    cur_token ++;
+  }
+
+  * index = terminal;
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+int mailimf_ignore_field_parse(const char * message, size_t length,
+                              size_t * index)
+{
+  int has_field;
+  size_t cur_token;
+  int state;
+  size_t terminal;
+
+  has_field = FALSE;
+  cur_token = * index;
+
+  terminal = cur_token;
+  state = UNSTRUCTURED_START;
+
+  /* check if this is not a beginning CRLF */
+
+  if (cur_token >= length)
+    return MAILIMF_ERROR_PARSE;
+
+  switch (message[cur_token]) {
+  case '\r':
+    return MAILIMF_ERROR_PARSE;
+  case '\n':
+    return MAILIMF_ERROR_PARSE;
+  }
+
+  while (state != UNSTRUCTURED_OUT) {
+
+    switch(state) {
+    case UNSTRUCTURED_START:
+      if (cur_token >= length)
+       return MAILIMF_ERROR_PARSE;
+
+      switch(message[cur_token]) {
+      case '\r':
+       state = UNSTRUCTURED_CR;
+       break;
+      case '\n':
+       state = UNSTRUCTURED_LF;
+       break;
+      case ':':
+       has_field = TRUE;
+       state = UNSTRUCTURED_START;
+       break;
+      default:
+       state = UNSTRUCTURED_START;
+       break;
+      }
+      break;
+    case UNSTRUCTURED_CR:
+      if (cur_token >= length)
+       return MAILIMF_ERROR_PARSE;
+
+      switch(message[cur_token]) {
+      case '\n':
+       state = UNSTRUCTURED_LF;
+       break;
+      case ':':
+       has_field = TRUE;
+       state = UNSTRUCTURED_START;
+       break;
+      default:
+       state = UNSTRUCTURED_START;
+       break;
+      }
+      break;
+    case UNSTRUCTURED_LF:
+      if (cur_token >= length) {
+       terminal = cur_token;
+       state = UNSTRUCTURED_OUT;
+       break;
+      }
+
+      switch(message[cur_token]) {
+      case '\t':
+      case ' ':
+       state = UNSTRUCTURED_WSP;
+       break;
+      default:
+       terminal = cur_token;
+       state = UNSTRUCTURED_OUT;
+       break;
+      }
+      break;
+    case UNSTRUCTURED_WSP:
+      if (cur_token >= length)
+       return MAILIMF_ERROR_PARSE;
+
+      switch(message[cur_token]) {
+      case '\r':
+       state = UNSTRUCTURED_CR;
+       break;
+      case '\n':
+       state = UNSTRUCTURED_LF;
+       break;
+      case ':':
+       has_field = TRUE;
+       state = UNSTRUCTURED_START;
+       break;
+      default:
+       state = UNSTRUCTURED_START;
+       break;
+      }
+      break;
+    }
+
+    cur_token ++;
+  }
+
+  if (!has_field)
+    return MAILIMF_ERROR_PARSE;
+
+  * index = terminal;
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+/*
+date-time       =       [ day-of-week "," ] date FWS time [CFWS]
+*/
+
+int mailimf_date_time_parse(const char * message, size_t length,
+                           size_t * index,
+                           struct mailimf_date_time ** result)
+{
+  size_t cur_token;
+  int day_of_week;
+  struct mailimf_date_time * date_time;
+  int day;
+  int month;
+  int year;
+  int hour;
+  int min;
+  int sec;
+  int zone;
+  int r;
+
+  cur_token = * index;
+
+  day_of_week = -1;
+  r = mailimf_day_of_week_parse(message, length, &cur_token, &day_of_week);
+  if (r == MAILIMF_NO_ERROR) {
+    r = mailimf_comma_parse(message, length, &cur_token);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+  else if (r != MAILIMF_ERROR_PARSE)
+    return r;
+
+  r = mailimf_date_parse(message, length, &cur_token, &day, &month, &year);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_fws_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_time_parse(message, length, &cur_token,
+                        &hour, &min, &sec, &zone);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  date_time = mailimf_date_time_new(day, month, year, hour, min, sec, zone);
+  if (date_time == NULL)
+    return MAILIMF_ERROR_MEMORY;
+
+  * index = cur_token;
+  * result = date_time;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+day-of-week     =       ([FWS] day-name) / obs-day-of-week
+*/
+
+static int mailimf_day_of_week_parse(const char * message, size_t length,
+                                    size_t * index, int * result)
+{
+  size_t cur_token;
+  int day_of_week;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_day_name_parse(message, length, &cur_token, &day_of_week);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * index = cur_token;
+  * result = day_of_week;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+day-name        =       "Mon" / "Tue" / "Wed" / "Thu" /
+                        "Fri" / "Sat" / "Sun"
+*/
+
+struct mailimf_token_value {
+  int value;
+  char * str;
+};
+
+static struct mailimf_token_value day_names[] = {
+  {1, "Mon"},
+  {2, "Tue"},
+  {3, "Wed"},
+  {4, "Thu"},
+  {5, "Fri"},
+  {6, "Sat"},
+  {7, "Sun"},
+};
+
+enum {
+  DAY_NAME_START,
+  DAY_NAME_T,
+  DAY_NAME_S
+};
+
+static int guess_day_name(const char * message, size_t length, size_t index)
+{
+  int state;
+
+  state = DAY_NAME_START;
+
+  while (1) {
+
+    if (index >= length)
+      return -1;
+
+    switch(state) {
+    case DAY_NAME_START:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'M': /* Mon */
+       return 1;
+       break;
+      case 'T': /* Tue Thu */
+       state = DAY_NAME_T;
+       break;
+      case 'W': /* Wed */
+       return 3;
+      case 'F':
+       return 5;
+      case 'S': /* Sat Sun */
+       state = DAY_NAME_S;
+       break;
+      default:
+       return -1;
+      }
+      break;
+    case DAY_NAME_T:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'U':
+       return 2;
+      case 'H':
+       return 4;
+      default:
+       return -1;
+      }
+      break;
+    case DAY_NAME_S:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'A':
+       return 6;
+      case 'U':
+       return 7;
+      default:
+       return -1;
+      }
+      break;
+    }
+
+    index ++;
+  }
+}
+
+static int mailimf_day_name_parse(const char * message, size_t length,
+                                 size_t * index, int * result)
+{
+  size_t cur_token;
+  int day_of_week;
+  int guessed_day;
+  int r;
+
+  cur_token = * index;
+
+  guessed_day = guess_day_name(message, length, cur_token);
+  if (guessed_day == -1)
+    return MAILIMF_ERROR_PARSE;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token,
+                                          day_names[guessed_day - 1].str);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  day_of_week = guessed_day;
+
+  * result = day_of_week;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+date            =       day month year
+*/
+
+static int mailimf_date_parse(const char * message, size_t length,
+                             size_t * index,
+                             int * pday, int * pmonth, int * pyear)
+{
+  size_t cur_token;
+  int day;
+  int month;
+  int year;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_day_parse(message, length, &cur_token, &day);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_month_parse(message, length, &cur_token, &month);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_year_parse(message, length, &cur_token, &year);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * pday = day;
+  * pmonth = month;
+  * pyear = year;
+
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+year            =       4*DIGIT / obs-year
+*/
+
+static int mailimf_year_parse(const char * message, size_t length,
+                             size_t * index, int * result)
+{
+  uint32_t number;
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_number_parse(message, length, &cur_token, &number);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * index = cur_token;
+  * result = number;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+month           =       (FWS month-name FWS) / obs-month
+*/
+
+static int mailimf_month_parse(const char * message, size_t length,
+                              size_t * index, int * result)
+{
+  size_t cur_token;
+  int month;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_month_name_parse(message, length, &cur_token, &month);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * result = month;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+month-name      =       "Jan" / "Feb" / "Mar" / "Apr" /
+                        "May" / "Jun" / "Jul" / "Aug" /
+                        "Sep" / "Oct" / "Nov" / "Dec"
+*/
+
+static struct mailimf_token_value month_names[] = {
+  {1, "Jan"},
+  {2, "Feb"},
+  {3, "Mar"},
+  {4, "Apr"},
+  {5, "May"},
+  {6, "Jun"},
+  {7, "Jul"},
+  {8, "Aug"},
+  {9, "Sep"},
+  {10, "Oct"},
+  {11, "Nov"},
+  {12, "Dec"},
+};
+
+enum {
+  MONTH_START,
+  MONTH_J,
+  MONTH_JU,
+  MONTH_M,
+  MONTH_MA,
+  MONTH_A
+};
+
+static int guess_month(const char * message, size_t length, size_t index)
+{
+  int state;
+
+  state = MONTH_START;
+
+  while (1) {
+
+    if (index >= length)
+      return -1;
+
+    switch(state) {
+    case MONTH_START:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'J': /* Jan Jun Jul */
+       state = MONTH_J;
+       break;
+      case 'F': /* Feb */
+       return 2;
+      case 'M': /* Mar May */
+       state = MONTH_M;
+       break;
+      case 'A': /* Apr Aug */
+       state = MONTH_A;
+       break;
+      case 'S': /* Sep */
+       return 9;
+      case 'O': /* Oct */
+       return 10;
+      case 'N': /* Nov */
+       return 11;
+      case 'D': /* Dec */
+       return 12;
+      default:
+       return -1;
+      }
+      break;
+    case MONTH_J:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'A':
+       return 1;
+      case 'U':
+       state = MONTH_JU;
+       break;
+      default:
+       return -1;
+      }
+      break;
+    case MONTH_JU:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'N':
+       return 6;
+      case 'L':
+       return 7;
+      default:
+       return -1;
+      }
+      break;
+    case MONTH_M:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'A':
+       state = MONTH_MA;
+       break;
+      default:
+       return -1;
+      }
+      break;
+    case MONTH_MA:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'Y':
+       return 5;
+      case 'R':
+       return 3;
+      default:
+       return -1;
+      }
+      break;
+    case MONTH_A:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'P':
+       return 4;
+      case 'U':
+       return 8;
+      default:
+       return -1;
+      }
+      break;
+    }
+
+    index ++;
+  }
+}
+
+static int mailimf_month_name_parse(const char * message, size_t length,
+                                   size_t * index, int * result)
+{
+  size_t cur_token;
+  int month;
+  int guessed_month;
+  int r;
+
+  cur_token = * index;
+
+  guessed_month = guess_month(message, length, cur_token);
+  if (guessed_month == -1)
+    return MAILIMF_ERROR_PARSE;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token,
+                                          month_names[guessed_month - 1].str);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  month = guessed_month;
+
+  * result = month;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+day             =       ([FWS] 1*2DIGIT) / obs-day
+*/
+
+static int mailimf_day_parse(const char * message, size_t length,
+                            size_t * index, int * result)
+{
+  size_t cur_token;
+  uint32_t day;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_number_parse(message, length, &cur_token, &day);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * result = day;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+time            =       time-of-day FWS zone
+*/
+
+static int mailimf_time_parse(const char * message, size_t length,
+                             size_t * index, 
+                             int * phour, int * pmin,
+                             int * psec,
+                             int * pzone)
+{
+  size_t cur_token;
+  int hour;
+  int min;
+  int sec;
+  int zone;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_time_of_day_parse(message, length, &cur_token,
+                               &hour, &min, &sec);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_fws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_zone_parse(message, length, &cur_token, &zone);
+  if (r == MAILIMF_NO_ERROR) {
+    /* do nothing */
+  }
+  else if (r == MAILIMF_ERROR_PARSE) {
+    zone = 0;
+  }
+  else {
+    return r;
+  }
+
+  * phour = hour;
+  * pmin = min;
+  * psec = sec;
+  * pzone = zone;
+
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+time-of-day     =       hour ":" minute [ ":" second ]
+*/
+
+static int mailimf_time_of_day_parse(const char * message, size_t length,
+                                    size_t * index,
+                                    int * phour, int * pmin,
+                                    int * psec)
+{
+  int hour;
+  int min;
+  int sec;
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_hour_parse(message, length, &cur_token, &hour);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_minute_parse(message, length, &cur_token, &min);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r == MAILIMF_NO_ERROR) {
+    r = mailimf_second_parse(message, length, &cur_token, &sec);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+  else if (r == MAILIMF_ERROR_PARSE)
+    sec = 0;
+  else
+    return r;
+
+  * phour = hour;
+  * pmin = min;
+  * psec = sec;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+hour            =       2DIGIT / obs-hour
+*/
+
+static int mailimf_hour_parse(const char * message, size_t length,
+                             size_t * index, int * result)
+{
+  uint32_t hour;
+  int r;
+
+  r = mailimf_number_parse(message, length, index, &hour);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * result = hour;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+minute          =       2DIGIT / obs-minute
+*/
+
+static int mailimf_minute_parse(const char * message, size_t length,
+                               size_t * index, int * result)
+{
+  uint32_t minute;
+  int r;
+
+  r = mailimf_number_parse(message, length, index, &minute);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * result = minute;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+second          =       2DIGIT / obs-second
+*/
+
+static int mailimf_second_parse(const char * message, size_t length,
+                               size_t * index, int * result)
+{
+  uint32_t second;
+  int r;
+
+  r = mailimf_number_parse(message, length, index, &second);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * result = second;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+zone            =       (( "+" / "-" ) 4DIGIT) / obs-zone
+*/
+
+/*
+obs-zone        =       "UT" / "GMT" /          ; Universal Time
+                                                ; North American UT
+                                                ; offsets
+                        "EST" / "EDT" /         ; Eastern:  - 5/ - 4
+                        "CST" / "CDT" /         ; Central:  - 6/ - 5
+                        "MST" / "MDT" /         ; Mountain: - 7/ - 6
+                        "PST" / "PDT" /         ; Pacific:  - 8/ - 7
+
+                        %d65-73 /               ; Military zones - "A"
+                        %d75-90 /               ; through "I" and "K"
+                        %d97-105 /              ; through "Z", both
+                        %d107-122               ; upper and lower case
+*/
+
+enum {
+  STATE_ZONE_1 = 0,
+  STATE_ZONE_2 = 1,
+  STATE_ZONE_3 = 2,
+  STATE_ZONE_OK  = 3,
+  STATE_ZONE_ERR = 4,
+  STATE_ZONE_CONT = 5,
+};
+
+static int mailimf_zone_parse(const char * message, size_t length,
+                             size_t * index, int * result)
+{
+  uint32_t zone;
+  int sign;
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+  if (cur_token + 1 < length) {
+    if ((message[cur_token] == 'U') && (message[cur_token] == 'T')) {
+      * result = TRUE;
+      * index = cur_token + 2;
+
+      return MAILIMF_NO_ERROR;
+    }
+  }
+
+  if (cur_token + 2 < length) {
+    int state;
+
+    state = STATE_ZONE_1;
+    
+    while (state <= 2) {
+      switch (state) {
+      case STATE_ZONE_1:
+       switch (message[cur_token]) {
+       case 'G':
+         if (message[cur_token + 1] == 'M' && message[cur_token + 2] == 'T') {
+           zone = 0;
+           state = STATE_ZONE_OK;
+         }
+         else {
+           state = STATE_ZONE_ERR;
+         }
+         break;
+       case 'E':
+         zone = -5;
+         state = STATE_ZONE_2;
+         break;
+       case 'C':
+         zone = -6;
+         state = STATE_ZONE_2;
+         break;
+       case 'M':
+         zone = -7;
+         state = STATE_ZONE_2;
+         break;
+       case 'P':
+         zone = -8;
+         state = STATE_ZONE_2;
+         break;
+       default:
+         state = STATE_ZONE_CONT;
+         break;
+       }
+       break;
+      case STATE_ZONE_2:
+       switch (message[cur_token + 1]) {
+       case 'S':
+         state = STATE_ZONE_3;
+         break;
+       case 'D':
+         zone ++;
+         state = STATE_ZONE_3;
+         break;
+       default:
+         state = STATE_ZONE_ERR;
+         break;
+       }
+       break;
+      case STATE_ZONE_3:
+       if (message[cur_token + 2] == 'T') {
+         zone *= 100;
+         state = STATE_ZONE_OK;
+       }
+       else
+         state = STATE_ZONE_ERR;
+       break;
+      }
+    }
+
+    switch (state) {
+    case STATE_ZONE_OK:
+      * result = zone;
+      * index = cur_token + 3;
+      return MAILIMF_NO_ERROR;
+      
+    case STATE_ZONE_ERR:
+      return MAILIMF_ERROR_PARSE;
+    }
+  }
+
+  sign = 1;
+  r = mailimf_plus_parse(message, length, &cur_token);
+  if (r == MAILIMF_NO_ERROR)
+    sign = 1;
+
+  if (r == MAILIMF_ERROR_PARSE) {
+    r = mailimf_minus_parse(message, length, &cur_token);
+    if (r == MAILIMF_NO_ERROR)
+      sign = -1;
+  }
+
+  if (r == MAILIMF_NO_ERROR) {
+    /* do nothing */
+  }
+  else if (r == MAILIMF_ERROR_PARSE)
+    sign = 1;
+  else
+    return r;
+
+  r = mailimf_number_parse(message, length, &cur_token, &zone);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  zone = zone * sign;
+
+  * index = cur_token;
+  * result = zone;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+address         =       mailbox / group
+*/
+
+int mailimf_address_parse(const char * message, size_t length,
+                         size_t * index,
+                         struct mailimf_address ** result)
+{
+  int type;
+  size_t cur_token;
+  struct mailimf_mailbox * mailbox;
+  struct mailimf_group * group;
+  struct mailimf_address * address;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  mailbox = NULL;
+  group = NULL;
+
+  type = MAILIMF_ADDRESS_ERROR; /* XXX - removes a gcc warning */
+  r = mailimf_group_parse(message, length, &cur_token, &group);
+  if (r == MAILIMF_NO_ERROR)
+    type = MAILIMF_ADDRESS_GROUP;
+  
+  if (r == MAILIMF_ERROR_PARSE) {
+    r = mailimf_mailbox_parse(message, length, &cur_token, &mailbox);
+    if (r == MAILIMF_NO_ERROR)
+      type = MAILIMF_ADDRESS_MAILBOX;
+  }
+
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  address = mailimf_address_new(type, mailbox, group);
+  if (address == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free;
+  }
+
+  * result = address;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+  
+ free:
+  if (mailbox != NULL)
+    mailimf_mailbox_free(mailbox);
+  if (group != NULL)
+    mailimf_group_free(group);
+ err:
+  return res;
+}
+
+
+/*
+mailbox         =       name-addr / addr-spec
+*/
+
+
+int mailimf_mailbox_parse(const char * message, size_t length,
+                         size_t * index,
+                         struct mailimf_mailbox ** result)
+{
+  size_t cur_token;
+  char * display_name;
+  struct mailimf_mailbox * mailbox;
+  char * addr_spec;
+  int r;
+  int res;
+
+  cur_token = * index;
+  display_name = NULL;
+  addr_spec = NULL;
+
+  r = mailimf_name_addr_parse(message, length, &cur_token,
+                             &display_name, &addr_spec);
+  if (r == MAILIMF_ERROR_PARSE)
+    r = mailimf_addr_spec_parse(message, length, &cur_token, &addr_spec);
+
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  mailbox = mailimf_mailbox_new(display_name, addr_spec);
+  if (mailbox == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free;
+  }
+
+  * result = mailbox;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free:
+  if (display_name != NULL)
+    mailimf_display_name_free(display_name);
+  if (addr_spec != NULL)
+    mailimf_addr_spec_free(addr_spec);
+ err:
+  return res;
+}
+
+/*
+name-addr       =       [display-name] angle-addr
+*/
+
+static int mailimf_name_addr_parse(const char * message, size_t length,
+                                  size_t * index,
+                                  char ** pdisplay_name,
+                                  char ** pangle_addr)
+{
+  char * display_name;
+  char * angle_addr;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  display_name = NULL;
+  angle_addr = NULL;
+
+  r = mailimf_display_name_parse(message, length, &cur_token, &display_name);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_angle_addr_parse(message, length, &cur_token, &angle_addr);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_display_name;
+  }
+
+  * pdisplay_name = display_name;
+  * pangle_addr = angle_addr;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_display_name:
+  if (display_name != NULL)
+    mailimf_display_name_free(display_name);
+ err:
+  return res;
+}
+
+/*
+angle-addr      =       [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
+*/
+
+static int mailimf_angle_addr_parse(const char * message, size_t length,
+                                   size_t * index, char ** result)
+{
+  size_t cur_token;
+  char * addr_spec;
+  int r;
+  
+  cur_token = * index;
+  
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+  
+  r = mailimf_lower_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+  
+  r = mailimf_addr_spec_parse(message, length, &cur_token, &addr_spec);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+  
+  r = mailimf_greater_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    free(addr_spec);
+    return r;
+  }
+
+  * result = addr_spec;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+group           =       display-name ":" [mailbox-list / CFWS] ";"
+                        [CFWS]
+*/
+
+static int mailimf_group_parse(const char * message, size_t length,
+                              size_t * index,
+                              struct mailimf_group ** result)
+{
+  size_t cur_token;
+  char * display_name;
+  struct mailimf_mailbox_list * mailbox_list;
+  struct mailimf_group * group;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  mailbox_list = NULL;
+
+  r = mailimf_display_name_parse(message, length, &cur_token, &display_name);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_display_name;
+  }
+
+  r = mailimf_mailbox_list_parse(message, length, &cur_token, &mailbox_list);
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    break;
+  case MAILIMF_ERROR_PARSE:
+    r = mailimf_cfws_parse(message, length, &cur_token);
+    if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+      return r;
+    break;
+  default:
+    return r;
+  }
+
+  r = mailimf_semi_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_mailbox_list;
+  }
+
+  group = mailimf_group_new(display_name, mailbox_list);
+  if (group == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_mailbox_list;
+  }
+
+  * index = cur_token;
+  * result = group;
+
+  return MAILIMF_NO_ERROR;
+
+ free_mailbox_list:
+  mailimf_mailbox_list_free(mailbox_list);
+ free_display_name:
+  mailimf_display_name_free(display_name);
+ err:
+  return res;
+}
+
+/*
+display-name    =       phrase
+*/
+
+static int mailimf_display_name_parse(const char * message, size_t length,
+                                     size_t * index, char ** result)
+{
+  return mailimf_phrase_parse(message, length, index, result);
+}
+
+/*
+mailbox-list    =       (mailbox *("," mailbox)) / obs-mbox-list
+*/
+
+int
+mailimf_mailbox_list_parse(const char * message, size_t length,
+                          size_t * index,
+                          struct mailimf_mailbox_list ** result)
+{
+  size_t cur_token;
+  clist * list;
+  struct mailimf_mailbox_list * mailbox_list;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_struct_list_parse(message, length, 
+                               &cur_token, &list, ',',
+                               (mailimf_struct_parser *)
+                               mailimf_mailbox_parse,
+                               (mailimf_struct_destructor *)
+                               mailimf_mailbox_free);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  mailbox_list = mailimf_mailbox_list_new(list);
+  if (mailbox_list == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_list;
+  }
+
+  * result = mailbox_list;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_list:
+  clist_foreach(list, (clist_func) mailimf_mailbox_free, NULL);
+  clist_free(list);
+ err:
+  return res;
+}                                 
+
+/*
+address-list    =       (address *("," address)) / obs-addr-list
+*/
+
+
+int
+mailimf_address_list_parse(const char * message, size_t length,
+                          size_t * index,
+                          struct mailimf_address_list ** result)
+{
+  size_t cur_token;
+  clist * list;
+  struct mailimf_address_list * address_list;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_struct_list_parse(message, length,
+                               &cur_token, &list, ',',
+                               (mailimf_struct_parser *)
+                               mailimf_address_parse,
+                               (mailimf_struct_destructor *)
+                               mailimf_address_free);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  address_list = mailimf_address_list_new(list);
+  if (address_list == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_list;
+  }
+
+  * result = address_list;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_list:
+  clist_foreach(list, (clist_func) mailimf_address_free, NULL);
+  clist_free(list);
+ err:
+  return res;
+}                                 
+
+/*
+addr-spec       =       local-part "@" domain
+*/
+
+
+static int mailimf_addr_spec_parse(const char * message, size_t length,
+                                  size_t * index,
+                                  char ** result)
+{
+  size_t cur_token;
+#if 0
+  char * local_part;
+  char * domain;
+#endif
+  char * addr_spec;
+  int r;
+  int res;
+  size_t begin;
+  size_t end;
+  int final;
+  size_t count;
+  const char * src;
+  char * dest;
+  size_t i;
+  
+  cur_token = * index;
+  
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+
+  end = cur_token;
+  if (end >= length) {
+    res = MAILIMF_ERROR_PARSE;
+    goto err;
+  }
+
+  begin = cur_token;
+
+  final = FALSE;
+  while (1) {
+    switch (message[end]) {
+    case '>':
+    case ',':
+    case '\r':
+    case '\n':
+    case '(':
+    case ')':
+    case ':':
+    case ';':
+      final = TRUE;
+      break;
+    }
+
+    if (final)
+      break;
+
+    end ++;
+    if (end >= length)
+      break;
+  }
+
+  if (end == begin) {
+    res = MAILIMF_ERROR_PARSE;
+    goto err;
+  }
+  
+  addr_spec = malloc(end - cur_token + 1);
+  if (addr_spec == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+  
+  count = end - cur_token;
+  src = message + cur_token;
+  dest = addr_spec;
+  for(i = 0 ; i < count ; i ++) {
+    if ((* src != ' ') && (* src != '\t')) {
+      * dest = * src;
+      dest ++;
+    }
+    src ++;
+  }
+  * dest = '\0';
+  
+#if 0
+  strncpy(addr_spec, message + cur_token, end - cur_token);
+  addr_spec[end - cur_token] = '\0';
+#endif
+
+  cur_token = end;
+
+#if 0
+  r = mailimf_local_part_parse(message, length, &cur_token, &local_part);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_at_sign_parse(message, length, &cur_token);
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    r = mailimf_domain_parse(message, length, &cur_token, &domain);
+    if (r != MAILIMF_NO_ERROR) {
+      res = r;
+      goto free_local_part;
+    }
+    break;
+
+  case MAILIMF_ERROR_PARSE:
+    domain = NULL;
+    break;
+
+  default:
+    res = r;
+    goto free_local_part;
+  }
+
+  if (domain) {
+    addr_spec = malloc(strlen(local_part) + strlen(domain) + 2);
+    if (addr_spec == NULL) {
+      res = MAILIMF_ERROR_MEMORY;
+      goto free_domain;
+    }
+    
+    strcpy(addr_spec, local_part);
+    strcat(addr_spec, "@");
+    strcat(addr_spec, domain);
+
+    mailimf_domain_free(domain);
+    mailimf_local_part_free(local_part);
+  }
+  else {
+    addr_spec = local_part;
+  }
+#endif
+
+  * result = addr_spec;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+#if 0
+ free_domain:
+  mailimf_domain_free(domain);
+ free_local_part:
+  mailimf_local_part_free(local_part);
+#endif
+ err:
+  return res;
+}
+
+/*
+local-part      =       dot-atom / quoted-string / obs-local-part
+*/
+
+#if 0
+static int mailimf_local_part_parse(const char * message, size_t length,
+                                   size_t * index,
+                                   char ** result)
+{
+  int r;
+
+  r = mailimf_dot_atom_parse(message, length, index, result);
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    return r;
+  case MAILIMF_ERROR_PARSE:
+    break;
+  default:
+    return r;
+  }
+
+  r = mailimf_quoted_string_parse(message, length, index, result);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+/*
+domain          =       dot-atom / domain-literal / obs-domain
+*/
+
+#if 0
+static int mailimf_domain_parse(const char * message, size_t length,
+                               size_t * index,
+                               char ** result)
+{
+  int r;
+
+  r = mailimf_dot_atom_parse(message, length, index, result);
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    return r;
+  case MAILIMF_ERROR_PARSE:
+    break;
+  default:
+    return r;
+  }
+
+  r = mailimf_domain_literal_parse(message, length, index, result);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+/*
+[FWS] dcontent
+*/
+
+#if 0
+static int
+mailimf_domain_literal_fws_dcontent_parse(const char * message, size_t length,
+                                         size_t * index)
+{
+  size_t cur_token;
+  char ch;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+  
+  r = mailimf_dcontent_parse(message, length, &cur_token, &ch);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+/*
+domain-literal  =       [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
+*/
+
+#if 0
+static int mailimf_domain_literal_parse(const char * message, size_t length,
+                                       size_t * index, char ** result)
+{
+  size_t cur_token;
+  int len;
+  int begin;
+  char * domain_literal;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  begin = cur_token;
+  r = mailimf_obracket_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  while (1) {
+    r = mailimf_domain_literal_fws_dcontent_parse(message, length,
+                                                 &cur_token);
+    if (r == MAILIMF_NO_ERROR) {
+      /* do nothing */
+    }
+    else if (r == MAILIMF_ERROR_PARSE)
+      break;
+    else
+      return r;
+  }
+
+  r = mailimf_fws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_cbracket_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  len = cur_token - begin;
+
+  domain_literal = malloc(len + 1);
+  if (domain_literal == NULL)
+    return MAILIMF_ERROR_MEMORY;
+  strncpy(domain_literal, message + begin, len);
+  domain_literal[len] = '\0';
+
+  * result = domain_literal;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+/*
+dcontent        =       dtext / quoted-pair
+*/
+
+#if 0
+static int mailimf_dcontent_parse(const char * message, size_t length,
+                                 size_t * index, char * result)
+{
+  size_t cur_token;
+  char ch;
+  int r;
+  
+  cur_token = * index;
+
+  if (cur_token >= length)
+    return MAILIMF_ERROR_PARSE;
+
+  if (is_dtext(message[cur_token])) {
+    ch = message[cur_token];
+    cur_token ++;
+  }
+  else {
+    r = mailimf_quoted_pair_parse(message, length, &cur_token, &ch);
+    
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+    
+  * index = cur_token;
+  * result = ch;
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+
+/*
+dtext           =       NO-WS-CTL /     ; Non white space controls
+
+                        %d33-90 /       ; The rest of the US-ASCII
+                        %d94-126        ;  characters not including "[",
+                                        ;  "]", or "\"
+*/
+
+static inline int is_dtext(char ch)
+{
+  unsigned char uch = (unsigned char) ch;
+
+  if (is_no_ws_ctl(ch))
+    return TRUE;
+
+  if (uch < 33)
+    return FALSE;
+
+  if ((uch >= 91) && (uch <= 93))
+    return FALSE;
+
+  if (uch == 127)
+    return FALSE;
+
+  return TRUE;
+}
+
+/*
+message         =       (fields / obs-fields)
+                        [CRLF body]
+*/
+
+int mailimf_message_parse(const char * message, size_t length,
+                         size_t * index,
+                         struct mailimf_message ** result)
+{
+  struct mailimf_fields * fields;
+  struct mailimf_body * body;
+  struct mailimf_message * msg;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_fields_parse(message, length, &cur_token, &fields);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_crlf_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_body_parse(message, length, &cur_token, &body);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_fields;
+  }
+
+  msg = mailimf_message_new(fields, body);
+  if (msg == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_body;
+  }
+
+  * index = cur_token;
+  * result = msg;
+
+  return MAILIMF_NO_ERROR;
+
+ free_body:
+  mailimf_body_free(body);
+ free_fields:
+  mailimf_fields_free(fields);
+ err:
+  return res;
+}
+
+/*
+body            =       *(*998text CRLF) *998text
+*/
+
+int mailimf_body_parse(const char * message, size_t length,
+                      size_t * index,
+                      struct mailimf_body ** result)
+{
+  size_t cur_token;
+  struct mailimf_body * body;
+
+  cur_token = * index;
+
+  body = mailimf_body_new(message + cur_token, length - cur_token);
+  if (body == NULL)
+    return MAILIMF_ERROR_MEMORY;
+
+  cur_token = length;
+
+  * result = body;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+CHANGE TO THE RFC 2822
+
+original :
+
+fields          =       *(trace
+                          *(resent-date /
+                           resent-from /
+                           resent-sender /
+                           resent-to /
+                           resent-cc /
+                           resent-bcc /
+                           resent-msg-id))
+                        *(orig-date /
+                        from /
+                        sender /
+                        reply-to /
+                        to /
+                        cc /
+                        bcc /
+                        message-id /
+                        in-reply-to /
+                        references /
+                        subject /
+                        comments /
+                        keywords /
+                        optional-field)
+
+INTO THE FOLLOWING :
+*/
+
+/*
+resent-fields-list =      *(resent-date /
+                           resent-from /
+                           resent-sender /
+                           resent-to /
+                           resent-cc /
+                           resent-bcc /
+                           resent-msg-id))
+*/
+
+#if 0
+enum {
+  RESENT_HEADER_START,
+};
+
+static int guess_resent_header_type(char * message,
+                                   size_t length, size_t index)
+{
+  int r;
+
+  r = mailimf_token_case_insensitive_parse(message,
+                                          length, &index, "Resent-");
+  if (r != MAILIMF_NO_ERROR)
+    return MAILIMF_RESENT_FIELD_NONE;
+  
+  if (index >= length)
+    return MAILIMF_RESENT_FIELD_NONE;
+
+  switch(toupper(message[index])) {
+  case 'D':
+    return MAILIMF_RESENT_FIELD_DATE;
+  case 'F':
+    return MAILIMF_RESENT_FIELD_FROM;
+  case 'S':
+    return MAILIMF_RESENT_FIELD_SENDER;
+  case 'T':
+    return MAILIMF_RESENT_FIELD_TO;
+  case 'C':
+    return MAILIMF_RESENT_FIELD_CC;
+  case 'B':
+    return MAILIMF_RESENT_FIELD_BCC;
+  case 'M':
+    return MAILIMF_RESENT_FIELD_MSG_ID;
+  default:
+    return MAILIMF_RESENT_FIELD_NONE;
+  }
+}
+#endif
+
+#if 0
+static int
+mailimf_resent_field_parse(const char * message, size_t length,
+                          size_t * index,
+                          struct mailimf_resent_field ** result)
+{
+  struct mailimf_orig_date * resent_date;
+  struct mailimf_from * resent_from;
+  struct mailimf_sender * resent_sender;
+  struct mailimf_to* resent_to;
+  struct mailimf_cc * resent_cc;
+  struct mailimf_bcc * resent_bcc;
+  struct mailimf_message_id * resent_msg_id;
+  size_t cur_token;
+  int type;
+  struct mailimf_resent_field * resent_field;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  resent_date = NULL;
+  resent_from = NULL;
+  resent_sender = NULL;
+  resent_to = NULL;
+  resent_cc = NULL;
+  resent_bcc = NULL;
+  resent_msg_id = NULL;
+
+  type = guess_resent_header_type(message, length, cur_token);
+
+  switch(type) {
+  case MAILIMF_RESENT_FIELD_DATE:
+    r = mailimf_resent_date_parse(message, length, &cur_token,
+                                 &resent_date);
+    if (r != MAILIMF_NO_ERROR) {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_RESENT_FIELD_FROM:
+    r = mailimf_resent_from_parse(message, length, &cur_token,
+                                 &resent_from);
+    if (r != MAILIMF_NO_ERROR) {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_RESENT_FIELD_SENDER:
+    r = mailimf_resent_sender_parse(message, length, &cur_token,
+                                   &resent_sender);
+    if (r != MAILIMF_NO_ERROR) {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_RESENT_FIELD_TO:
+    r = mailimf_resent_to_parse(message, length, &cur_token,
+                               &resent_to);
+    if (r != MAILIMF_NO_ERROR) {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_RESENT_FIELD_CC:
+    r= mailimf_resent_cc_parse(message, length, &cur_token,
+                              &resent_cc);
+    if (r != MAILIMF_NO_ERROR) {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_RESENT_FIELD_BCC:
+    r = mailimf_resent_bcc_parse(message, length, &cur_token,
+                                &resent_bcc);
+    if (r != MAILIMF_NO_ERROR) {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_RESENT_FIELD_MSG_ID:
+    r = mailimf_resent_msg_id_parse(message, length, &cur_token,
+                                   &resent_msg_id);
+    if (r != MAILIMF_NO_ERROR) {
+      res = r;
+      goto err;
+    }
+    break;
+  default:
+    res = MAILIMF_ERROR_PARSE;
+    goto err;
+  }
+
+  resent_field = mailimf_resent_field_new(type, resent_date,
+                                         resent_from, resent_sender,
+                                         resent_to, resent_cc,
+                                         resent_bcc, resent_msg_id);
+  if (resent_field == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_resent;
+  }
+
+  * result = resent_field;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_resent:
+  if (resent_msg_id != NULL)
+    mailimf_message_id_free(resent_msg_id);
+  if (resent_bcc != NULL)
+    mailimf_bcc_free(resent_bcc);
+  if (resent_cc != NULL)
+    mailimf_cc_free(resent_cc);
+  if (resent_to != NULL)
+    mailimf_to_free(resent_to);
+  if (resent_sender != NULL)
+    mailimf_sender_free(resent_sender);
+  if (resent_from != NULL)
+    mailimf_from_free(resent_from);
+  if (resent_date != NULL)
+    mailimf_orig_date_free(resent_date);
+ err:
+  return res;
+}
+#endif
+
+#if 0
+static int
+mailimf_resent_fields_list_parse(const char * message, size_t length,
+                                size_t * index,
+                                struct mailimf_resent_fields_list ** result)
+{
+  clist * list;
+  size_t cur_token;
+  struct mailimf_resent_fields_list * resent_fields_list;
+  int r;
+  int res;
+
+  cur_token = * index;
+  list = NULL;
+
+  r = mailimf_struct_multiple_parse(message, length, &cur_token, &list,
+                                   (mailimf_struct_parser *)
+                                   mailimf_resent_field_parse,
+                                   (mailimf_struct_destructor *)
+                                   mailimf_resent_field_free);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  resent_fields_list = mailimf_resent_fields_list_new(list);
+  if (resent_fields_list == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_list;
+  }
+
+  * result = resent_fields_list;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_list:
+  clist_foreach(list, (clist_func) mailimf_resent_field_free, NULL);
+  clist_free(list);
+ err:
+  return res;
+}
+#endif
+
+/*
+ ([trace]
+  [resent-fields-list])
+*/
+
+#if 0
+static int
+mailimf_trace_resent_fields_parse(const char * message, size_t length,
+                                 size_t * index,
+                                 struct mailimf_trace_resent_fields ** result)
+{
+  size_t cur_token;
+  struct mailimf_return * return_path;
+  struct mailimf_resent_fields_list * resent_fields;
+  struct mailimf_trace_resent_fields * trace_resent_fields;
+  int res;
+  int r;
+
+  cur_token = * index;
+
+  return_path = NULL;
+  resent_fields = NULL;
+
+  r = mailimf_return_parse(message, length, &cur_token,
+                          &return_path);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_resent_fields_list_parse(message, length, &cur_token,
+                                      &resent_fields);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+
+  if ((return_path == NULL) && (resent_fields == NULL)) {
+    res = MAILIMF_ERROR_PARSE;
+    goto err;
+  }
+
+  trace_resent_fields = mailimf_trace_resent_fields_new(return_path,
+                                                       resent_fields);
+  if (trace_resent_fields == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_resent_fields;
+  }
+
+  * result = trace_resent_fields;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_resent_fields:
+  if (resent_fields != NULL)
+    mailimf_resent_fields_list_free(resent_fields);
+  if (return_path != NULL)
+    mailimf_return_free(return_path);
+ err:
+  return res;
+}
+#endif
+
+/*
+delivering-info =       *([trace]
+                          [resent-fields-list])
+*/
+
+#if 0
+static int
+mailimf_delivering_info_parse(const char * message, size_t length,
+                             size_t * index,
+                             struct mailimf_delivering_info ** result)
+{
+  size_t cur_token;
+  clist * list;
+  struct mailimf_delivering_info * delivering_info;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_struct_multiple_parse(message, length, &cur_token,
+                                   &list,
+                                   (mailimf_struct_parser *)
+                                   mailimf_trace_resent_fields_parse,
+                                   (mailimf_struct_destructor *)
+                                   mailimf_trace_resent_fields_free);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  delivering_info = mailimf_delivering_info_new(list);
+  if (delivering_info == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_list;
+  }
+
+  * result = delivering_info;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_list:
+  clist_foreach(list, (clist_func) mailimf_trace_resent_fields_free, NULL);
+  clist_free(list);
+ err:
+  return res;
+}
+#endif
+
+/*
+field           =       delivering-info /
+                        orig-date /
+                        from /
+                        sender /
+                        reply-to /
+                        to /
+                        cc /
+                        bcc /
+                        message-id /
+                        in-reply-to /
+                        references /
+                        subject /
+                        comments /
+                        keywords /
+                        optional-field
+*/
+
+enum {
+  HEADER_START,
+  HEADER_C,
+  HEADER_R,
+  HEADER_RE,
+  HEADER_S,
+  HEADER_RES,
+};
+
+static int guess_header_type(const char * message, size_t length, size_t index)
+{
+  int state;
+  int r;
+
+  state = HEADER_START;
+  
+  while (1) {
+
+    if (index >= length)
+      return MAILIMF_FIELD_NONE;
+
+    switch(state) {
+    case HEADER_START:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'B':
+       return MAILIMF_FIELD_BCC;
+      case 'C':
+       state = HEADER_C;
+       break;
+      case 'D':
+       return MAILIMF_FIELD_ORIG_DATE;
+      case 'F':
+       return MAILIMF_FIELD_FROM;
+      case 'I':
+       return MAILIMF_FIELD_IN_REPLY_TO;
+      case 'K':
+       return MAILIMF_FIELD_KEYWORDS;
+      case 'M':
+       return MAILIMF_FIELD_MESSAGE_ID;
+      case 'R':
+       state = HEADER_R;
+       break;
+      case 'T':
+       return MAILIMF_FIELD_TO;
+       break;
+      case 'S':
+       state = HEADER_S;
+       break;
+      default:
+       return MAILIMF_FIELD_NONE;
+      }
+      break;
+    case HEADER_C:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'O':
+       return MAILIMF_FIELD_COMMENTS;
+      case 'C':
+       return MAILIMF_FIELD_CC;
+      default:
+       return MAILIMF_FIELD_NONE;
+      }
+      break;
+    case HEADER_R:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'E':
+       state = HEADER_RE;
+       break;
+      default:
+       return MAILIMF_FIELD_NONE;
+      }
+      break;
+    case HEADER_RE:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'F':
+       return MAILIMF_FIELD_REFERENCES;
+      case 'P':
+       return MAILIMF_FIELD_REPLY_TO;
+      case 'S':
+        state = HEADER_RES;
+        break;
+      case 'T':
+        return MAILIMF_FIELD_RETURN_PATH;
+      default:
+       return MAILIMF_FIELD_NONE;
+      }
+      break;
+    case HEADER_S:
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'E':
+       return MAILIMF_FIELD_SENDER;
+      case 'U':
+       return MAILIMF_FIELD_SUBJECT;
+      default:
+       return MAILIMF_FIELD_NONE;
+      }
+      break;
+
+    case HEADER_RES:
+      r = mailimf_token_case_insensitive_parse(message,
+          length, &index, "ent-");
+      if (r != MAILIMF_NO_ERROR)
+        return MAILIMF_FIELD_NONE;
+      
+      if (index >= length)
+        return MAILIMF_FIELD_NONE;
+      
+      switch((char) toupper((unsigned char) message[index])) {
+      case 'D':
+        return MAILIMF_FIELD_RESENT_DATE;
+      case 'F':
+        return MAILIMF_FIELD_RESENT_FROM;
+      case 'S':
+        return MAILIMF_FIELD_RESENT_SENDER;
+      case 'T':
+        return MAILIMF_FIELD_RESENT_TO;
+      case 'C':
+        return MAILIMF_FIELD_RESENT_CC;
+      case 'B':
+        return MAILIMF_FIELD_RESENT_BCC;
+      case 'M':
+        return MAILIMF_FIELD_RESENT_MSG_ID;
+      default:
+        return MAILIMF_FIELD_NONE;
+      }
+      break;
+    }
+    index ++;
+  }
+}
+
+static int mailimf_field_parse(const char * message, size_t length,
+                              size_t * index,
+                              struct mailimf_field ** result)
+{
+  size_t cur_token;
+  int type;
+  struct mailimf_return * return_path;
+  struct mailimf_orig_date * resent_date;
+  struct mailimf_from * resent_from;
+  struct mailimf_sender * resent_sender;
+  struct mailimf_to* resent_to;
+  struct mailimf_cc * resent_cc;
+  struct mailimf_bcc * resent_bcc;
+  struct mailimf_message_id * resent_msg_id;
+  struct mailimf_orig_date * orig_date;
+  struct mailimf_from * from;
+  struct mailimf_sender * sender;
+  struct mailimf_reply_to * reply_to;
+  struct mailimf_to * to;
+  struct mailimf_cc * cc;
+  struct mailimf_bcc * bcc;
+  struct mailimf_message_id * message_id;
+  struct mailimf_in_reply_to * in_reply_to;
+  struct mailimf_references * references;
+  struct mailimf_subject * subject;
+  struct mailimf_comments * comments;
+  struct mailimf_keywords * keywords;
+  struct mailimf_optional_field * optional_field;
+  struct mailimf_field * field;
+  int guessed_type;
+  int r;
+  int res;
+  
+  cur_token = * index;
+
+  return_path = NULL;
+  resent_date = NULL;
+  resent_from = NULL;
+  resent_sender = NULL;
+  resent_to = NULL;
+  resent_cc = NULL;
+  resent_bcc = NULL;
+  resent_msg_id = NULL;
+  orig_date = NULL;
+  from = NULL;
+  sender = NULL;
+  reply_to = NULL;
+  to = NULL;
+  cc = NULL;
+  bcc = NULL;
+  message_id = NULL;
+  in_reply_to = NULL;
+  references = NULL;
+  subject = NULL;
+  comments = NULL;
+  keywords = NULL;
+  optional_field = NULL;
+
+  guessed_type = guess_header_type(message, length, cur_token);
+  type = MAILIMF_FIELD_NONE;
+
+  switch (guessed_type) {
+  case MAILIMF_FIELD_ORIG_DATE:
+    r = mailimf_orig_date_parse(message, length, &cur_token,
+                               &orig_date);
+    if (r == MAILIMF_NO_ERROR)
+      type = MAILIMF_FIELD_ORIG_DATE;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_FROM:
+    r = mailimf_from_parse(message, length, &cur_token,
+                          &from);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_SENDER:
+    r = mailimf_sender_parse(message, length, &cur_token,
+                            &sender);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_REPLY_TO:
+    r = mailimf_reply_to_parse(message, length, &cur_token,
+                              &reply_to);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_TO:
+    r = mailimf_to_parse(message, length, &cur_token,
+                        &to);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_CC:
+    r = mailimf_cc_parse(message, length, &cur_token,
+                        &cc);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_BCC:
+    r = mailimf_bcc_parse(message, length, &cur_token,
+                         &bcc);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_MESSAGE_ID:
+    r = mailimf_message_id_parse(message, length, &cur_token,
+                                &message_id);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_IN_REPLY_TO:
+    r = mailimf_in_reply_to_parse(message, length, &cur_token,
+                                 &in_reply_to);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_REFERENCES:
+    r = mailimf_references_parse(message, length, &cur_token,
+                                &references);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_SUBJECT:
+    r = mailimf_subject_parse(message, length, &cur_token,
+                             &subject);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_COMMENTS:
+    r = mailimf_comments_parse(message, length, &cur_token,
+                              &comments);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_KEYWORDS:
+    r = mailimf_keywords_parse(message, length, &cur_token,
+                              &keywords);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_RETURN_PATH:
+    r = mailimf_return_parse(message, length, &cur_token,
+        &return_path);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_RESENT_DATE:
+    r = mailimf_resent_date_parse(message, length, &cur_token,
+                                 &resent_date);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_RESENT_FROM:
+    r = mailimf_resent_from_parse(message, length, &cur_token,
+                                 &resent_from);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_RESENT_SENDER:
+    r = mailimf_resent_sender_parse(message, length, &cur_token,
+                                   &resent_sender);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_RESENT_TO:
+    r = mailimf_resent_to_parse(message, length, &cur_token,
+                               &resent_to);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_RESENT_CC:
+    r= mailimf_resent_cc_parse(message, length, &cur_token,
+                              &resent_cc);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_RESENT_BCC:
+    r = mailimf_resent_bcc_parse(message, length, &cur_token,
+                                &resent_bcc);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_RESENT_MSG_ID:
+    r = mailimf_resent_msg_id_parse(message, length, &cur_token,
+                                   &resent_msg_id);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  }
+
+  if (type == MAILIMF_FIELD_NONE) {
+    r = mailimf_optional_field_parse(message, length, &cur_token,
+        &optional_field);
+    if (r != MAILIMF_NO_ERROR) {
+      res = r;
+      goto err;
+    }
+
+    type = MAILIMF_FIELD_OPTIONAL_FIELD;
+  }
+
+  field = mailimf_field_new(type, return_path, resent_date,
+      resent_from, resent_sender, resent_to, resent_cc, resent_bcc,
+      resent_msg_id, orig_date, from, sender, reply_to, to,
+      cc, bcc, message_id, in_reply_to, references,
+      subject, comments, keywords, optional_field);
+  if (field == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_field;
+  }
+
+  * result = field;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_field:
+  if (return_path != NULL)
+    mailimf_return_free(return_path);
+  if (resent_date != NULL)
+    mailimf_orig_date_free(resent_date);
+  if (resent_from != NULL)
+    mailimf_from_free(resent_from);
+  if (resent_sender != NULL)
+    mailimf_sender_free(resent_sender);
+  if (resent_to != NULL)
+    mailimf_to_free(resent_to);
+  if (resent_cc != NULL)
+    mailimf_cc_free(resent_cc);
+  if (resent_bcc != NULL)
+    mailimf_bcc_free(resent_bcc);
+  if (resent_msg_id != NULL)
+    mailimf_message_id_free(resent_msg_id);
+  if (orig_date != NULL)
+    mailimf_orig_date_free(orig_date);
+  if (from != NULL)
+    mailimf_from_free(from);
+  if (sender != NULL)
+    mailimf_sender_free(sender);
+  if (reply_to != NULL)
+    mailimf_reply_to_free(reply_to);
+  if (to != NULL)
+    mailimf_to_free(to);
+  if (cc != NULL)
+    mailimf_cc_free(cc);
+  if (bcc != NULL)
+    mailimf_bcc_free(bcc);
+  if (message_id != NULL)
+    mailimf_message_id_free(message_id);
+  if (in_reply_to != NULL)
+    mailimf_in_reply_to_free(in_reply_to);
+  if (references != NULL)
+    mailimf_references_free(references);
+  if (subject != NULL)
+    mailimf_subject_free(subject);
+  if (comments != NULL)
+    mailimf_comments_free(comments);
+  if (keywords != NULL)
+    mailimf_keywords_free(keywords);
+  if (optional_field != NULL)
+    mailimf_optional_field_free(optional_field);
+ err:
+  return res;
+}
+
+
+/*
+fields          =       *(delivering-info /
+                       orig-date /
+                        from /
+                        sender /
+                        reply-to /
+                        to /
+                        cc /
+                        bcc /
+                        message-id /
+                        in-reply-to /
+                        references /
+                        subject /
+                        comments /
+                        keywords /
+                        optional-field)
+*/
+
+#if 0
+int
+mailimf_unparsed_fields_parse(const char * message, size_t length,
+                             size_t * index,
+                             struct mailimf_unparsed_fields ** result)
+{
+  size_t cur_token;
+  clist * list;
+  struct mailimf_unparsed_fields * fields;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  list = NULL;
+
+  r = mailimf_struct_multiple_parse(message, length, &cur_token,
+                                   &list,
+                                   (mailimf_struct_parser *)
+                                   mailimf_optional_field_parse,
+                                   (mailimf_struct_destructor *)
+                                   mailimf_optional_field_free);
+  /*
+  if ((r = MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+  */
+
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    /* do nothing */
+    break;
+
+  case MAILIMF_ERROR_PARSE:
+    list = clist_new();
+    if (list == NULL) {
+      res = MAILIMF_ERROR_MEMORY;
+      goto err;
+    }
+    break;
+
+  default:
+    res = r;
+    goto err;
+  }
+
+  fields = mailimf_unparsed_fields_new(list);
+  if (fields == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free;
+  }
+
+  * result = fields;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free:
+  if (list != NULL) {
+    clist_foreach(list, (clist_func) mailimf_optional_field_free, NULL);
+    clist_free(list);
+  }
+ err:
+  return res;
+}
+#endif
+
+int mailimf_fields_parse(const char * message, size_t length,
+                        size_t * index,
+                        struct mailimf_fields ** result)
+{
+  size_t cur_token;
+  clist * list;
+  struct mailimf_fields * fields;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  list = NULL;
+
+  r = mailimf_struct_multiple_parse(message, length, &cur_token,
+                                   &list,
+                                   (mailimf_struct_parser *)
+                                   mailimf_field_parse,
+                                   (mailimf_struct_destructor *)
+                                   mailimf_field_free);
+  /*
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+  */
+
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    /* do nothing */
+    break;
+
+  case MAILIMF_ERROR_PARSE:
+    list = clist_new();
+    if (list == NULL) {
+      res = MAILIMF_ERROR_MEMORY;
+      goto err;
+    }
+    break;
+
+  default:
+    res = r;
+    goto err;
+  }
+
+  fields = mailimf_fields_new(list);
+  if (fields == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free;
+  }
+
+  * result = fields;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free:
+  if (list != NULL) {
+    clist_foreach(list, (clist_func) mailimf_field_free, NULL);
+    clist_free(list);
+  }
+ err:
+  return res;
+}
+
+/*
+orig-date       =       "Date:" date-time CRLF
+*/
+
+
+static int
+mailimf_orig_date_parse(const char * message, size_t length,
+                       size_t * index, struct mailimf_orig_date ** result)
+{
+  struct mailimf_date_time * date_time;
+  struct mailimf_orig_date * orig_date;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Date:");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_date_time_parse(message, length, &cur_token, &date_time);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_ignore_unstructured_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_date_time;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_date_time;
+  }
+
+  orig_date = mailimf_orig_date_new(date_time);
+  if (orig_date == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_date_time;
+  }
+
+  * result = orig_date;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_date_time:
+  mailimf_date_time_free(date_time);
+ err:
+  return res;
+}
+
+/*
+from            =       "From:" mailbox-list CRLF
+*/
+
+static int
+mailimf_from_parse(const char * message, size_t length,
+                  size_t * index, struct mailimf_from ** result)
+{
+  struct mailimf_mailbox_list * mb_list;
+  struct mailimf_from * from;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token =  * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "From");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_mailbox_list_parse(message, length, &cur_token, &mb_list);
+
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_mb_list;
+  }
+
+  from = mailimf_from_new(mb_list);
+  if (from == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_mb_list;
+  }
+
+  * result = from;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_mb_list:
+  mailimf_mailbox_list_free(mb_list);
+ err:
+  return res;
+}
+
+/*
+sender          =       "Sender:" mailbox CRLF
+*/
+
+static int
+mailimf_sender_parse(const char * message, size_t length,
+                    size_t * index, struct mailimf_sender ** result)
+{
+  struct mailimf_mailbox * mb;
+  struct mailimf_sender * sender;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Sender");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_mailbox_parse(message, length, &cur_token, &mb);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_mb;
+  }
+
+  sender = mailimf_sender_new(mb);
+  if (sender == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_mb;
+  }
+
+  * result = sender;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_mb:
+  mailimf_mailbox_free(mb);
+ err:
+  return res;
+}
+
+/*
+reply-to        =       "Reply-To:" address-list CRLF
+*/
+
+
+static int
+mailimf_reply_to_parse(const char * message, size_t length,
+                      size_t * index, struct mailimf_reply_to ** result)
+{
+  struct mailimf_address_list * addr_list;
+  struct mailimf_reply_to * reply_to;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Reply-To");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_address_list_parse(message, length, &cur_token, &addr_list);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_addr_list;
+  }
+
+  reply_to = mailimf_reply_to_new(addr_list);
+  if (reply_to == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_addr_list;
+  }
+
+  * result = reply_to;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_addr_list:
+  mailimf_address_list_free(addr_list);
+ err:
+  return res;
+}
+
+/*
+to              =       "To:" address-list CRLF
+*/
+
+static int
+mailimf_to_parse(const char * message, size_t length,
+                size_t * index, struct mailimf_to ** result)
+{
+  struct mailimf_address_list * addr_list;
+  struct mailimf_to * to;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "To");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_address_list_parse(message, length, &cur_token, &addr_list);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_addr_list;
+  }
+
+  to = mailimf_to_new(addr_list);
+  if (to == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_addr_list;
+  }
+
+  * result = to;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_addr_list:
+  mailimf_address_list_free(addr_list);
+ err:
+  return res;
+}
+
+/*
+cc              =       "Cc:" address-list CRLF
+*/
+
+
+static int
+mailimf_cc_parse(const char * message, size_t length,
+                size_t * index, struct mailimf_cc ** result)
+{
+  struct mailimf_address_list * addr_list;
+  struct mailimf_cc * cc;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Cc");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_address_list_parse(message, length, &cur_token, &addr_list);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_addr_list;
+  }
+
+  cc = mailimf_cc_new(addr_list);
+  if (cc == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_addr_list;
+  }
+
+  * result = cc;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_addr_list:
+  mailimf_address_list_free(addr_list);
+ err:
+  return res;
+}
+
+/*
+bcc             =       "Bcc:" (address-list / [CFWS]) CRLF
+*/
+
+
+static int
+mailimf_bcc_parse(const char * message, size_t length,
+                 size_t * index, struct mailimf_bcc ** result)
+{
+  struct mailimf_address_list * addr_list;
+  struct mailimf_bcc * bcc;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+  addr_list = NULL;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Bcc");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_address_list_parse(message, length, &cur_token, &addr_list);
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    /* do nothing */
+    break;
+  case MAILIMF_ERROR_PARSE:
+    r = mailimf_cfws_parse(message, length, &cur_token);
+    if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+      res = r;
+      goto err;
+    }
+    break;
+  default:
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_addr_list;
+  }
+
+  bcc = mailimf_bcc_new(addr_list);
+  if (bcc == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_addr_list;
+  }
+
+  * result = bcc;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_addr_list:
+  mailimf_address_list_free(addr_list);
+ err:
+  return res;
+}
+
+/*
+message-id      =       "Message-ID:" msg-id CRLF
+*/
+
+static int mailimf_message_id_parse(const char * message, size_t length,
+                                   size_t * index,
+                                   struct mailimf_message_id ** result)
+{
+  char * value;
+  size_t cur_token;
+  struct mailimf_message_id * message_id;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Message-ID");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_msg_id_parse(message, length, &cur_token, &value);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_value;
+  }
+
+  message_id = mailimf_message_id_new(value);
+  if (message_id == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_value;
+  }
+
+  * result = message_id;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_value:
+  mailimf_msg_id_free(value);
+ err:
+  return res;
+}
+
+/*
+in-reply-to     =       "In-Reply-To:" 1*msg-id CRLF
+*/
+
+int mailimf_msg_id_list_parse(const char * message, size_t length,
+                             size_t * index, clist ** result)
+{
+  return mailimf_struct_multiple_parse(message, length, index,
+                                      result,
+                                      (mailimf_struct_parser *)
+                                      mailimf_unstrict_msg_id_parse,
+                                      (mailimf_struct_destructor *)
+                                      mailimf_msg_id_free);
+}
+
+static int mailimf_in_reply_to_parse(const char * message, size_t length,
+                                    size_t * index,
+                                    struct mailimf_in_reply_to ** result)
+{
+  struct mailimf_in_reply_to * in_reply_to;
+  size_t cur_token;
+  clist * msg_id_list;
+  int res;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "In-Reply-To");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_msg_id_list_parse(message, length, &cur_token, &msg_id_list);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_list;
+  }
+
+  in_reply_to = mailimf_in_reply_to_new(msg_id_list);
+  if (in_reply_to == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_list;
+  }
+
+  * result = in_reply_to;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_list:
+  clist_foreach(msg_id_list, (clist_func) mailimf_msg_id_free, NULL);
+  clist_free(msg_id_list);
+ err:
+  return res;
+}
+
+/*
+references      =       "References:" 1*msg-id CRLF
+*/
+
+int mailimf_references_parse(const char * message, size_t length,
+                            size_t * index,
+                            struct mailimf_references ** result)
+{
+  struct mailimf_references * references;
+  size_t cur_token;
+  clist * msg_id_list;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "References");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_msg_id_list_parse(message, length, &cur_token, &msg_id_list);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_list;
+  }
+
+  references = mailimf_references_new(msg_id_list);
+  if (references == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_list;
+  }
+
+  * result = references;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_list:
+  clist_foreach(msg_id_list, (clist_func) mailimf_msg_id_free, NULL);
+  clist_free(msg_id_list);
+ err:
+  return res;
+}
+
+/*
+msg-id          =       [CFWS] "<" id-left "@" id-right ">" [CFWS]
+*/
+
+int mailimf_msg_id_parse(const char * message, size_t length,
+                        size_t * index,
+                        char ** result)
+{
+  size_t cur_token;
+#if 0
+  char * id_left;
+  char * id_right;
+#endif
+  char * msg_id;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_lower_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_addr_spec_parse(message, length, &cur_token, &msg_id);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+  
+  r = mailimf_greater_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    free(msg_id);
+    res = r;
+    goto err;
+  }
+
+#if 0
+  r = mailimf_id_left_parse(message, length, &cur_token, &id_left);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_at_sign_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_id_left;
+  }
+
+  r = mailimf_id_right_parse(message, length, &cur_token, &id_right);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_id_left;
+  }
+
+  r = mailimf_greater_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_id_right;
+  }
+
+  msg_id = malloc(strlen(id_left) + strlen(id_right) + 2);
+  if (msg_id == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_id_right;
+  }
+  strcpy(msg_id, id_left);
+  strcat(msg_id, "@");
+  strcat(msg_id, id_right);
+
+  mailimf_id_left_free(id_left);
+  mailimf_id_right_free(id_right);
+#endif
+
+  * result = msg_id;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+#if 0
+ free_id_right:
+  mailimf_id_right_free(id_right);
+ free_id_left:
+  mailimf_id_left_free(id_left);
+#endif
+  /*
+ free:
+  mailimf_atom_free(msg_id);
+  */
+ err:
+  return res;
+}
+
+static int mailimf_parse_unwanted_msg_id(const char * message, size_t length,
+                                        size_t * index)
+{
+  size_t cur_token;
+  int r;
+  char * word;
+  int token_parsed;
+
+  cur_token = * index;
+
+  token_parsed = TRUE;
+  while (token_parsed) {
+    token_parsed = FALSE;
+    r = mailimf_word_parse(message, length, &cur_token, &word);
+    if (r == MAILIMF_NO_ERROR) {
+      mailimf_word_free(word);
+      token_parsed = TRUE;
+    }
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else
+      return r;
+    r = mailimf_semi_colon_parse(message, length, &cur_token);
+    if (r == MAILIMF_NO_ERROR)
+      token_parsed = TRUE;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else
+      return r;
+    r = mailimf_comma_parse(message, length, &cur_token);
+    if (r == MAILIMF_NO_ERROR)
+      token_parsed = TRUE;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else
+      return r;
+    r = mailimf_plus_parse(message, length, &cur_token);
+    if (r == MAILIMF_NO_ERROR)
+      token_parsed = TRUE;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else
+      return r;
+    r = mailimf_colon_parse(message, length, &cur_token);
+    if (r == MAILIMF_NO_ERROR)
+      token_parsed = TRUE;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else
+      return r;
+    r = mailimf_point_parse(message, length, &cur_token);
+    if (r == MAILIMF_NO_ERROR)
+      token_parsed = TRUE;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else
+      return r;
+    r = mailimf_at_sign_parse(message, length, &cur_token);
+    if (r == MAILIMF_NO_ERROR)
+      token_parsed = TRUE;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else
+      return r;
+  }
+
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_unstrict_msg_id_parse(const char * message, size_t length,
+                                        size_t * index,
+                                        char ** result)
+{
+  char * msgid;
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_parse_unwanted_msg_id(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_msg_id_parse(message, length, &cur_token, &msgid);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_parse_unwanted_msg_id(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  * result = msgid;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+id-left         =       dot-atom-text / no-fold-quote / obs-id-left
+*/
+
+#if 0
+static int mailimf_id_left_parse(const char * message, size_t length,
+                                size_t * index, char ** result)
+{
+  int r;
+
+  r = mailimf_dot_atom_text_parse(message, length, index, result);
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    return MAILIMF_NO_ERROR;
+  case MAILIMF_ERROR_PARSE:
+    break;
+  default:
+    return r;
+  }
+  
+  r = mailimf_no_fold_quote_parse(message, length, index, result);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+/*
+id-right        =       dot-atom-text / no-fold-literal / obs-id-right
+*/
+
+#if 0
+static int mailimf_id_right_parse(const char * message, size_t length,
+                                 size_t * index, char ** result)
+{
+  int r;
+
+  r = mailimf_dot_atom_text_parse(message, length, index, result);
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    return MAILIMF_NO_ERROR;
+  case MAILIMF_ERROR_PARSE:
+    break;
+  default:
+    return r;
+  }
+
+  r = mailimf_no_fold_literal_parse(message, length, index, result);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+/*
+no-fold-quote   =       DQUOTE *(qtext / quoted-pair) DQUOTE
+*/
+
+#if 0
+static int mailimf_no_fold_quote_char_parse(const char * message, size_t length,
+                                           size_t * index, char * result)
+{
+  char ch;
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+#if 0
+  r = mailimf_qtext_parse(message, length, &cur_token, &ch);
+#endif
+
+  if (cur_token >= length)
+    return MAILIMF_ERROR_PARSE;
+
+  if (is_qtext(message[cur_token])) {
+    ch = message[cur_token];
+    cur_token ++;
+  }
+  else {
+    r = mailimf_quoted_pair_parse(message, length, &cur_token, &ch);
+    
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  * index = cur_token;
+  * result = ch;
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+#if 0
+static int mailimf_no_fold_quote_parse(const char * message, size_t length,
+                                      size_t * index, char ** result)
+{
+  size_t cur_token;
+  size_t begin;
+  char ch;
+  char * no_fold_quote;
+  int r;
+  int res;
+
+  begin = cur_token;
+  r = mailimf_dquote_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  while (1) {
+    r = mailimf_no_fold_quote_char_parse(message, length, &cur_token, &ch);
+    if (r == MAILIMF_NO_ERROR) {
+      /* do nothing */
+    }
+    else if (r == MAILIMF_ERROR_PARSE)
+      break;
+    else {
+      res = r;
+      goto err;
+    }
+  }
+
+  r = mailimf_dquote_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  /*  no_fold_quote = strndup(message + begin, cur_token - begin); */
+  no_fold_quote = malloc(cur_token - begin + 1);
+  if (no_fold_quote == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+  strncpy(no_fold_quote, message + begin, cur_token - begin);
+  no_fold_quote[cur_token - begin] = '\0';
+
+  * result = no_fold_quote;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ err:
+  return res;
+}
+#endif
+
+/*
+no-fold-literal =       "[" *(dtext / quoted-pair) "]"
+*/
+
+#if 0
+static inline int
+mailimf_no_fold_literal_char_parse(const char * message, size_t length,
+                                  size_t * index, char * result)
+{
+  char ch;
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+#if 0
+  r = mailimf_dtext_parse(message, length, &cur_token, &ch);
+#endif
+  if (cur_token >= length)
+    return MAILIMF_ERROR_PARSE;
+
+  if (is_dtext(message[cur_token])) {
+    ch = message[cur_token];
+    cur_token ++;
+  }
+  else {
+    r = mailimf_quoted_pair_parse(message, length, &cur_token, &ch);
+    
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  * index = cur_token;
+  * result = ch;
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+#if 0
+static int mailimf_no_fold_literal_parse(const char * message, size_t length,
+                                        size_t * index, char ** result)
+{
+  size_t cur_token;
+  size_t begin;
+  char ch;
+  char * no_fold_literal;
+  int r;
+  int res;
+
+  begin = cur_token;
+  r = mailimf_obracket_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  while (1) {
+    r = mailimf_no_fold_literal_char_parse(message, length,
+                                          &cur_token, &ch);
+    if (r == MAILIMF_NO_ERROR) {
+      /* do nothing */
+    }
+    else if (r == MAILIMF_ERROR_PARSE)
+      break;
+    else {
+      res = r;
+      goto err;
+    }
+  }
+
+  r = mailimf_cbracket_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  /*
+  no_fold_literal = strndup(message + begin, cur_token - begin);
+  */
+  no_fold_literal = malloc(cur_token - begin + 1);
+  if (no_fold_literal == NULL) {
+    res = MAILIMF_NO_ERROR;
+    goto err;
+  }
+  strncpy(no_fold_literal, message + begin, cur_token - begin);
+  no_fold_literal[cur_token - begin] = '\0';
+
+  * result = no_fold_literal;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ err:
+  return res;
+}
+#endif
+
+/*
+subject         =       "Subject:" unstructured CRLF
+*/
+
+static int mailimf_subject_parse(const char * message, size_t length,
+                                size_t * index,
+                                struct mailimf_subject ** result)
+{
+  struct mailimf_subject * subject;
+  char * value;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Subject");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+  
+  r = mailimf_unstructured_parse(message, length, &cur_token, &value);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_value;
+  }
+  
+  subject = mailimf_subject_new(value);
+  if (subject == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_value;
+  }
+
+  * result = subject;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_value:
+  mailimf_unstructured_free(value);
+ err:
+  return res;
+}
+
+/*
+comments        =       "Comments:" unstructured CRLF
+*/
+
+static int mailimf_comments_parse(const char * message, size_t length,
+                                 size_t * index,
+                                 struct mailimf_comments ** result)
+{
+  struct mailimf_comments * comments;
+  char * value;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Comments");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+  
+  r = mailimf_unstructured_parse(message, length, &cur_token, &value);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_value;
+  }
+  
+  comments = mailimf_comments_new(value);
+  if (comments == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_value;
+  }
+
+  * result = comments;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_value:
+  mailimf_unstructured_free(value);
+ err:
+  return res;
+}
+
+/*
+keywords        =       "Keywords:" phrase *("," phrase) CRLF
+*/
+
+static int mailimf_keywords_parse(const char * message, size_t length,
+                                 size_t * index,
+                                 struct mailimf_keywords ** result)
+{
+  struct mailimf_keywords * keywords;
+  clist * list;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Keywords");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+  
+  r = mailimf_struct_list_parse(message, length, &cur_token,
+                               &list, ',',
+                               (mailimf_struct_parser *)
+                               mailimf_phrase_parse,
+                               (mailimf_struct_destructor *)
+                               mailimf_phrase_free);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_list;
+  }
+  
+  keywords = mailimf_keywords_new(list);
+  if (keywords == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_list;
+  }
+
+  * result = keywords;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_list:
+  clist_foreach(list, (clist_func) mailimf_phrase_free, NULL);
+  clist_free(list);
+ err:
+  return res;
+}
+
+/*
+resent-date     =       "Resent-Date:" date-time CRLF
+*/
+
+static int
+mailimf_resent_date_parse(const char * message, size_t length,
+                         size_t * index, struct mailimf_orig_date ** result)
+{
+  struct mailimf_orig_date * orig_date;
+  struct mailimf_date_time * date_time;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Resent-Date");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_date_time_parse(message, length, &cur_token, &date_time);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_date_time;
+  }
+
+  orig_date = mailimf_orig_date_new(date_time);
+  if (orig_date == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_date_time;
+  }
+
+  * result = orig_date;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_date_time:
+  mailimf_date_time_free(date_time);
+ err:
+  return res;
+}
+
+/*
+resent-from     =       "Resent-From:" mailbox-list CRLF
+*/
+
+static int
+mailimf_resent_from_parse(const char * message, size_t length,
+                         size_t * index, struct mailimf_from ** result)
+{
+  struct mailimf_mailbox_list * mb_list;
+  struct mailimf_from * from;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token =  * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Resent-From");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_mailbox_list_parse(message, length, &cur_token, &mb_list);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_mb_list;
+  }
+
+  from = mailimf_from_new(mb_list);
+  if (from == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_mb_list;
+  }
+
+  * result = from;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_mb_list:
+  mailimf_mailbox_list_free(mb_list);
+ err:
+  return res;
+}
+
+/*
+resent-sender   =       "Resent-Sender:" mailbox CRLF
+*/
+
+static int
+mailimf_resent_sender_parse(const char * message, size_t length,
+                           size_t * index, struct mailimf_sender ** result)
+{
+  struct mailimf_mailbox * mb;
+  struct mailimf_sender * sender;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = length;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Resent-Sender");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_mailbox_parse(message, length, &cur_token, &mb);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_mb;
+  }
+
+  sender = mailimf_sender_new(mb);
+  if (sender == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_mb;
+  }
+
+  * result = sender;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_mb:
+  mailimf_mailbox_free(mb);
+ err:
+  return res;
+}
+
+/*
+resent-to       =       "Resent-To:" address-list CRLF
+*/
+
+static int
+mailimf_resent_to_parse(const char * message, size_t length,
+                       size_t * index, struct mailimf_to ** result)
+{
+  struct mailimf_address_list * addr_list;
+  struct mailimf_to * to;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Resent-To");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_address_list_parse(message, length, &cur_token, &addr_list);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_addr_list;
+  }
+
+  to = mailimf_to_new(addr_list);
+  if (to == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_addr_list;
+  }
+
+  * result = to;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_addr_list:
+  mailimf_address_list_free(addr_list);
+ err:
+  return res;
+}
+
+/*
+resent-cc       =       "Resent-Cc:" address-list CRLF
+*/
+
+static int
+mailimf_resent_cc_parse(const char * message, size_t length,
+                       size_t * index, struct mailimf_cc ** result)
+{
+  struct mailimf_address_list * addr_list;
+  struct mailimf_cc * cc;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Resent-Cc");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_address_list_parse(message, length, &cur_token, &addr_list);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_addr_list;
+  }
+
+  cc = mailimf_cc_new(addr_list);
+  if (cc == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_addr_list;
+  }
+
+  * result = cc;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_addr_list:
+  mailimf_address_list_free(addr_list);
+ err:
+  return res;
+}
+
+/*
+resent-bcc      =       "Resent-Bcc:" (address-list / [CFWS]) CRLF
+*/
+
+static int
+mailimf_resent_bcc_parse(const char * message, size_t length,
+                        size_t * index, struct mailimf_bcc ** result)
+{
+  struct mailimf_address_list * addr_list;
+  struct mailimf_bcc * bcc;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+  bcc = NULL;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Resent-Bcc");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_address_list_parse(message, length, &cur_token, &addr_list);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_addr_list;
+  }
+
+  bcc = mailimf_bcc_new(addr_list);
+  if (bcc == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_addr_list;
+  }
+
+  * result = bcc;
+  * index = cur_token;
+
+  return TRUE;
+
+ free_addr_list:
+  mailimf_address_list_free(addr_list);
+ err:
+  return res;
+}
+
+/*
+resent-msg-id   =       "Resent-Message-ID:" msg-id CRLF
+*/
+
+static int
+mailimf_resent_msg_id_parse(const char * message, size_t length,
+                           size_t * index,
+                           struct mailimf_message_id ** result)
+{
+  char * value;
+  size_t cur_token;
+  struct mailimf_message_id * message_id;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Resent-Message-ID");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_msg_id_parse(message, length, &cur_token, &value);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_value;
+  }
+
+  message_id = mailimf_message_id_new(value);
+  if (message_id == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_value;
+  }
+
+  * result = message_id;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_value:
+  mailimf_msg_id_free(value);
+ err:
+  return res;
+}
+
+/*
+trace           =       [return]
+                        1*received
+*/
+
+#if 0
+static int mailimf_trace_parse(const char * message, size_t length,
+                              size_t * index,
+                              struct mailimf_trace ** result)
+{
+  size_t cur_token;
+  struct mailimf_return * return_path;
+  clist * received_list;
+  struct mailimf_trace * trace;
+  int r;
+  int res;
+
+  cur_token = * index;
+  return_path = NULL;
+  received_list = NULL;
+
+  r = mailimf_return_parse(message, length, &cur_token, &return_path);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_struct_multiple_parse(message, length, &cur_token,
+                                   &received_list,
+                                   (mailimf_struct_parser *)
+                                   mailimf_received_parse,
+                                   (mailimf_struct_destructor *)
+                                   mailimf_received_free);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+
+  if ((received_list == NULL) && (return_path == NULL)) {
+    res = MAILIMF_ERROR_PARSE;
+    goto free_return;
+  }
+
+  trace = mailimf_trace_new(return_path, received_list);
+  if (trace == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_list;
+  }
+
+  * result = trace;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_list:
+  clist_foreach(received_list, (clist_func) mailimf_received_free, NULL);
+  clist_free(received_list);
+ free_return:
+  if (return_path != NULL)
+    mailimf_return_free(return_path);
+ err:
+  return res;
+}
+#endif
+
+/*
+return          =       "Return-Path:" path CRLF
+*/
+
+static int mailimf_return_parse(const char * message, size_t length,
+                               size_t * index,
+                               struct mailimf_return ** result)
+{
+  struct mailimf_path * path = NULL;
+  struct mailimf_return * return_path;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Return-Path");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_path_parse(message, length, &cur_token, &path);
+  if ( r!= MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_path;
+  }
+
+  return_path = mailimf_return_new(path);
+  if (return_path == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_path;
+  }
+
+  * result = return_path;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_path:
+  mailimf_path_free(path);
+ err:
+  return res;
+}
+
+/*
+path            =       ([CFWS] "<" ([CFWS] / addr-spec) ">" [CFWS]) /
+                        obs-path
+*/
+
+static int mailimf_path_parse(const char * message, size_t length,
+                             size_t * index, struct mailimf_path ** result)
+{
+  size_t cur_token;
+  char * addr_spec;
+  struct mailimf_path * path;
+  int res;
+  int r;
+
+  cur_token = * index;
+  addr_spec = NULL;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_lower_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_addr_spec_parse(message, length, &cur_token, &addr_spec);
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    break;
+  case MAILIMF_ERROR_PARSE:
+    r = mailimf_cfws_parse(message, length, &cur_token);
+    if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+      res = r;
+      goto err;
+    }
+    break;
+  default:
+    return r;
+  }
+  
+  r = mailimf_greater_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  path = mailimf_path_new(addr_spec);
+  if (path == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_addr_spec;
+  }
+
+  * index = cur_token;
+  * result = path;
+
+  return MAILIMF_NO_ERROR;
+
+ free_addr_spec:
+  if (addr_spec == NULL)
+    mailimf_addr_spec_free(addr_spec);
+ err:
+  return res;
+}
+
+/*
+received        =       "Received:" name-val-list ";" date-time CRLF
+*/
+
+#if 0
+static int mailimf_received_parse(const char * message, size_t length,
+                                 size_t * index,
+                                 struct mailimf_received ** result)
+{
+  size_t cur_token;
+  struct mailimf_received * received;
+  struct mailimf_name_val_list * name_val_list;
+  struct mailimf_date_time * date_time;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_token_case_insensitive_parse(message, length,
+                                          &cur_token, "Received");
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+  
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_name_val_list_parse(message, length,
+                                 &cur_token, &name_val_list);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_semi_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_name_val_list;
+  }
+
+  r = mailimf_date_time_parse(message, length, &cur_token, &date_time);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_name_val_list;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_date_time;
+  }
+
+  received = mailimf_received_new(name_val_list, date_time);
+  if (received == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_date_time;
+  }
+
+  * index = cur_token;
+  * result = received;
+
+  return MAILIMF_NO_ERROR;
+
+ free_date_time:
+  mailimf_date_time_free(date_time);
+ free_name_val_list:
+  mailimf_name_val_list_free(name_val_list);
+ err:
+  return res;
+}
+#endif
+
+/*
+name-val-list   =       [CFWS] [name-val-pair *(CFWS name-val-pair)]
+*/
+
+#if 0
+static int
+mailimf_name_val_list_parse(const char * message, size_t length,
+                           size_t * index,
+                           struct mailimf_name_val_list ** result)
+{
+  size_t cur_token;
+  struct mailimf_name_val_pair * pair;
+  struct mailimf_name_val_list * name_val_list;
+  clist* list;
+  int res;
+  int r;
+
+  cur_token = * index;
+  list = NULL;
+
+  r = mailimf_name_val_pair_parse(message, length, &cur_token, &pair);
+
+  if (r == MAILIMF_NO_ERROR){
+    size_t final_token;
+
+    list = clist_new();
+    if (list == NULL) {
+      mailimf_name_val_pair_free(pair);
+      res = MAILIMF_ERROR_MEMORY;
+      goto err;
+    }
+
+    r = clist_append(list, pair);
+    if (r < 0) {
+      mailimf_name_val_pair_free(pair);
+      res = MAILIMF_ERROR_MEMORY;
+      goto free_list;
+    }
+
+    final_token = cur_token;
+    
+    while (1) {
+      r = mailimf_cfws_parse(message, length, &cur_token);
+      if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+       res = r;
+       goto free_list;
+      }
+
+      r = mailimf_name_val_pair_parse(message, length, &cur_token, &pair);
+      if (r == MAILIMF_NO_ERROR) {
+       /* do nothing */
+      }
+      else if (r == MAILIMF_ERROR_PARSE)
+       break;
+      else {
+       res = r;
+       goto free_list;
+      }
+
+      r = clist_append(list, pair);
+      if (r < 0) {
+       mailimf_name_val_pair_free(pair);
+       res = MAILIMF_ERROR_MEMORY;
+       goto free_list;
+      }
+
+      final_token = cur_token;
+    }
+    cur_token = final_token;
+  }
+
+  name_val_list = mailimf_name_val_list_new(list);
+  if (name_val_list == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_list;
+  }
+
+  * index = cur_token;
+  * result = name_val_list;
+
+  return MAILIMF_NO_ERROR;
+
+ free_list:
+  if (list != NULL) {
+    clist_foreach(list, (clist_func) mailimf_name_val_pair_free, NULL);
+    clist_free(list);
+  }
+ err:
+  return res;
+}
+#endif
+
+/*
+name-val-pair   =       item-name CFWS item-value
+*/
+
+#if 0
+static int
+mailimf_name_val_pair_parse(const char * message, size_t length,
+                           size_t * index,
+                           struct mailimf_name_val_pair ** result)
+{
+  size_t cur_token;
+  char * item_name;
+  struct mailimf_item_value * item_value;
+  struct mailimf_name_val_pair * name_val_pair;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE)) {
+    res = r;
+    goto err;
+  }
+  
+  r = mailimf_item_name_parse(message, length, &cur_token, &item_name);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_item_name;
+  }
+
+  r = mailimf_item_value_parse(message, length, &cur_token, &item_value);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_item_name;
+  }
+
+  name_val_pair = mailimf_name_val_pair_new(item_name, item_value);
+  if (name_val_pair == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_item_value;
+  }
+
+  * result = name_val_pair;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_item_value:
+  mailimf_item_value_free(item_value);
+ free_item_name:
+  mailimf_item_name_free(item_name);
+ err:
+  return res;
+}
+#endif
+
+/*
+item-name       =       ALPHA *(["-"] (ALPHA / DIGIT))
+*/
+
+#if 0
+static int mailimf_item_name_parse(const char * message, size_t length,
+                                  size_t * index, char ** result)
+{
+  size_t cur_token;
+  size_t begin;
+  char * item_name;
+  char ch;
+  int digit;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  begin = cur_token;
+
+  r = mailimf_alpha_parse(message, length, &cur_token, &ch);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  while (1) {
+    int minus_sign;
+
+    minus_sign = mailimf_minus_parse(message, length, &cur_token);
+
+    r = mailimf_alpha_parse(message, length, &cur_token, &ch);
+    if (r == MAILIMF_ERROR_PARSE)
+      r = mailimf_digit_parse(message, length, &cur_token, &digit);
+
+    if (r == MAILIMF_NO_ERROR) {
+      /* do nothing */
+    }
+    if (r == MAILIMF_ERROR_PARSE)
+      break;
+    else if (r != MAILIMF_NO_ERROR) {
+      res = r;
+      goto err;
+    }
+  }
+
+  item_name = strndup(message + begin, cur_token - begin);
+  if (item_name == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+
+  * index = cur_token;
+  * result = item_name;
+
+  return MAILIMF_NO_ERROR;
+
+ err:
+  return res;
+}
+#endif
+
+/*
+item-value      =       1*angle-addr / addr-spec /
+                         atom / domain / msg-id
+*/
+
+#if 0
+static int is_item_value_atext(char ch)
+{
+  switch (ch) {
+  case '\t':
+  case ' ':
+  case '\r':
+  case '\n':
+  case ';':
+    return FALSE;
+  default:
+    return TRUE;
+  }
+}
+
+static int mailimf_item_value_atom_parse(const char * message, size_t length,
+                                        size_t * index, char ** result)
+{
+  char * atom;
+  size_t cur_token;
+  int r;
+
+  cur_token = * index;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  r = mailimf_custom_string_parse(message, length, &cur_token,
+                                 &atom, is_item_value_atext);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_cfws_parse(message, length, &cur_token);
+  if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
+    return r;
+
+  * index = cur_token;
+  * result = atom;
+
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_item_value_parse(const char * message, size_t length,
+                                   size_t * index,
+                                   struct mailimf_item_value ** result)
+{
+  size_t cur_token;
+  clist * angle_addr_list;
+  char * addr_spec;
+  char * atom;
+  char * domain;
+  char * msg_id;
+  int type;
+  struct mailimf_item_value * item_value;
+  int r;
+  int res;
+
+  cur_token = * index;
+  
+  angle_addr_list = NULL;
+  addr_spec = NULL;
+  atom = NULL;
+  domain = NULL;
+  msg_id = NULL;
+
+  r = mailimf_struct_multiple_parse(message, length, &cur_token,
+                                   &angle_addr_list,
+                                   (mailimf_struct_parser *)
+                                   mailimf_angle_addr_parse,
+                                   (mailimf_struct_destructor *)
+                                   mailimf_angle_addr_free);
+  if (r == MAILIMF_NO_ERROR)
+    type = MAILIMF_ITEM_VALUE_ANGLE_ADDR_LIST;
+
+  if (r == MAILIMF_ERROR_PARSE) {
+    r = mailimf_addr_spec_parse(message, length, &cur_token,
+                               &addr_spec);
+    if (r == MAILIMF_NO_ERROR)
+      type = MAILIMF_ITEM_VALUE_ADDR_SPEC;
+  }
+
+  if (r == MAILIMF_ERROR_PARSE) {
+    r = mailimf_msg_id_parse(message, length, &cur_token,
+                            &msg_id);
+    if (r == MAILIMF_NO_ERROR)
+      type = MAILIMF_ITEM_VALUE_MSG_ID;
+  }
+
+  /*
+  else if (mailimf_domain_parse(message, length, &cur_token,
+                               &domain))
+    type = MAILIMF_ITEM_VALUE_DOMAIN;
+  */
+  /*
+  else if (mailimf_atom_parse(message, length, &cur_token,
+                             &atom))
+    type = MAILIMF_ITEM_VALUE_ATOM;
+  */
+
+  if (r == MAILIMF_ERROR_PARSE) {
+    r = mailimf_item_value_atom_parse(message, length, &cur_token,
+                                     &atom);
+    if (r == MAILIMF_NO_ERROR)
+      type = MAILIMF_ITEM_VALUE_ATOM;
+  }
+
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  item_value = mailimf_item_value_new(type, angle_addr_list, addr_spec,
+                                     atom, domain, msg_id);
+  if (item_value == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free;
+  }
+
+  * result = item_value;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free:
+  if (angle_addr_list != NULL) {
+    clist_foreach(angle_addr_list, (clist_func) mailimf_angle_addr_free, NULL);
+    clist_free(angle_addr_list);
+  }
+  if (addr_spec != NULL)
+    mailimf_addr_spec_free(addr_spec);
+  if (atom != NULL)
+    mailimf_atom_free(atom);
+  if (domain != NULL)
+    mailimf_domain_free(domain);
+  if (msg_id != NULL)
+    mailimf_msg_id_free(msg_id);
+ err:
+  return res;
+}
+#endif
+
+/*
+optional-field  =       field-name ":" unstructured CRLF
+*/
+
+static int
+mailimf_optional_field_parse(const char * message, size_t length,
+                            size_t * index,
+                            struct mailimf_optional_field ** result)
+{
+  char * name;
+  char * value;
+  struct mailimf_optional_field * optional_field;
+  size_t cur_token;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  r = mailimf_field_name_parse(message, length, &cur_token, &name);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = mailimf_colon_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_name;
+  }
+
+  r = mailimf_unstructured_parse(message, length, &cur_token, &value);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_name;
+  }
+
+  r = mailimf_unstrict_crlf_parse(message, length, &cur_token);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_value;
+  }
+
+  optional_field = mailimf_optional_field_new(name, value);
+  if (optional_field == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_value;
+  }
+
+  * result = optional_field;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_value:
+  mailimf_unstructured_free(value);
+ free_name:
+  mailimf_field_name_free(name);
+ err:
+  return res;
+}
+     
+/*
+field-name      =       1*ftext
+*/
+
+static inline int is_ftext(char ch);
+
+static int mailimf_field_name_parse(const char * message, size_t length,
+                                   size_t * index, char ** result)
+{
+  char * field_name;
+  size_t cur_token;
+  size_t end;
+  
+  cur_token = * index;
+
+  end = cur_token;
+  if (end >= length) {
+    return MAILIMF_ERROR_PARSE;
+  }
+
+  while (is_ftext(message[end])) {
+    end ++;
+    if (end >= length)
+      break;
+  }
+  if (end == cur_token) {
+    return MAILIMF_ERROR_PARSE;
+  }
+
+  /*  field_name = strndup(message + cur_token, end - cur_token); */
+  field_name = malloc(end - cur_token + 1);
+  if (field_name == NULL) {
+    return MAILIMF_ERROR_MEMORY;
+  }
+  strncpy(field_name, message + cur_token, end - cur_token);
+  field_name[end - cur_token] = '\0';
+
+  cur_token = end;
+  
+  * index = cur_token;
+  * result = field_name;
+  
+  return MAILIMF_NO_ERROR;
+}
+
+/*
+ftext           =       %d33-57 /               ; Any character except
+                        %d59-126                ;  controls, SP, and
+                                                ;  ":".
+*/
+
+static inline int is_ftext(char ch)
+{
+  unsigned char uch = (unsigned char) ch;
+
+  if (uch < 33)
+    return FALSE;
+
+  if (uch == 58)
+    return FALSE;
+
+  return TRUE;
+}
+
+/*
+static int mailimf_ftext_parse(const char * message, size_t length,
+                                   size_t * index, gchar * result)
+{
+  return mailimf_typed_text_parse(message, length, index, result, is_ftext);
+}
+*/
+
+
+
+
+static int mailimf_envelope_field_parse(const char * message, size_t length,
+                                       size_t * index,
+                                       struct mailimf_field ** result)
+{
+  size_t cur_token;
+  int type;
+  struct mailimf_orig_date * orig_date;
+  struct mailimf_from * from;
+  struct mailimf_sender * sender;
+  struct mailimf_reply_to * reply_to;
+  struct mailimf_to * to;
+  struct mailimf_cc * cc;
+  struct mailimf_bcc * bcc;
+  struct mailimf_message_id * message_id;
+  struct mailimf_in_reply_to * in_reply_to;
+  struct mailimf_references * references;
+  struct mailimf_subject * subject;
+  struct mailimf_optional_field * optional_field;
+  struct mailimf_field * field;
+  int guessed_type;
+  int r;
+  int res;
+  
+  cur_token = * index;
+
+  orig_date = NULL;
+  from = NULL;
+  sender = NULL;
+  reply_to = NULL;
+  to = NULL;
+  cc = NULL;
+  bcc = NULL;
+  message_id = NULL;
+  in_reply_to = NULL;
+  references = NULL;
+  subject = NULL;
+  optional_field = NULL;
+
+  guessed_type = guess_header_type(message, length, cur_token);
+  type = MAILIMF_FIELD_NONE;
+
+  switch (guessed_type) {
+  case MAILIMF_FIELD_ORIG_DATE:
+    r = mailimf_orig_date_parse(message, length, &cur_token,
+                               &orig_date);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_FROM:
+    r = mailimf_from_parse(message, length, &cur_token,
+                          &from);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_SENDER:
+    r = mailimf_sender_parse(message, length, &cur_token,
+                            &sender);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_REPLY_TO:
+    r = mailimf_reply_to_parse(message, length, &cur_token,
+                              &reply_to);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_TO:
+    r = mailimf_to_parse(message, length, &cur_token,
+                        &to);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_CC:
+    r = mailimf_cc_parse(message, length, &cur_token,
+                        &cc);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_BCC:
+    r = mailimf_bcc_parse(message, length, &cur_token,
+                         &bcc);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_MESSAGE_ID:
+    r = mailimf_message_id_parse(message, length, &cur_token,
+                                &message_id);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_IN_REPLY_TO:
+    r = mailimf_in_reply_to_parse(message, length, &cur_token,
+                                 &in_reply_to);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_REFERENCES:
+    r = mailimf_references_parse(message, length, &cur_token,
+                                &references);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  case MAILIMF_FIELD_SUBJECT:
+    r = mailimf_subject_parse(message, length, &cur_token,
+                             &subject);
+    if (r == MAILIMF_NO_ERROR)
+      type = guessed_type;
+    else if (r == MAILIMF_ERROR_PARSE) {
+      /* do nothing */
+    }
+    else {
+      res = r;
+      goto err;
+    }
+    break;
+  }
+
+  if (type == MAILIMF_FIELD_NONE) {
+    res = MAILIMF_ERROR_PARSE;
+    goto err;
+  }
+
+  field = mailimf_field_new(type, NULL, NULL, NULL, NULL, NULL,
+      NULL, NULL, NULL,
+      orig_date, from, sender, reply_to, to,
+      cc, bcc, message_id, in_reply_to, references,
+      subject, NULL, NULL, optional_field);
+  if (field == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_field;
+  }
+  
+  * result = field;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free_field:
+  if (orig_date != NULL)
+    mailimf_orig_date_free(orig_date);
+  if (from != NULL)
+    mailimf_from_free(from);
+  if (sender != NULL)
+    mailimf_sender_free(sender);
+  if (reply_to != NULL)
+    mailimf_reply_to_free(reply_to);
+  if (to != NULL)
+    mailimf_to_free(to);
+  if (cc != NULL)
+    mailimf_cc_free(cc);
+  if (bcc != NULL)
+    mailimf_bcc_free(bcc);
+  if (message_id != NULL)
+    mailimf_message_id_free(message_id);
+  if (in_reply_to != NULL)
+    mailimf_in_reply_to_free(in_reply_to);
+  if (references != NULL)
+    mailimf_references_free(references);
+  if (subject != NULL)
+    mailimf_subject_free(subject);
+  if (optional_field != NULL)
+    mailimf_optional_field_free(optional_field);
+ err:
+  return res;
+}
+
+int mailimf_envelope_fields_parse(const char * message, size_t length,
+                                 size_t * index,
+                                 struct mailimf_fields ** result)
+{
+  size_t cur_token;
+  clist * list;
+  struct mailimf_fields * fields;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  list = clist_new();
+  if (list == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+
+  while (1) {
+    struct mailimf_field * elt;
+
+    r = mailimf_envelope_field_parse(message, length, &cur_token, &elt);
+    if (r == MAILIMF_NO_ERROR) {
+      r = clist_append(list, elt);
+      if (r < 0) {
+       res = MAILIMF_ERROR_MEMORY;
+       goto free;
+      }
+    }
+    else if (r == MAILIMF_ERROR_PARSE) {
+      r = mailimf_ignore_field_parse(message, length, &cur_token);
+      if (r == MAILIMF_NO_ERROR) {
+       /* do nothing */
+      }
+      else if (r == MAILIMF_ERROR_PARSE) {
+       break;
+      }
+      else {
+       res = r;
+       goto free;
+      }
+    }
+    else {
+      res = r;
+      goto free;
+    }
+  }
+
+  fields = mailimf_fields_new(list);
+  if (fields == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free;
+  }
+
+  * result = fields;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free:
+  if (list != NULL) {
+    clist_foreach(list, (clist_func) mailimf_field_free, NULL);
+    clist_free(list);
+  }
+ err:
+  return res;
+}
+
+
+static int
+mailimf_envelope_or_optional_field_parse(const char * message,
+                                        size_t length,
+                                        size_t * index,
+                                        struct mailimf_field ** result)
+{
+  int r;
+  size_t cur_token;
+  struct mailimf_optional_field * optional_field;
+  struct mailimf_field * field;
+
+  r = mailimf_envelope_field_parse(message, length, index, result);
+  if (r == MAILIMF_NO_ERROR)
+    return MAILIMF_NO_ERROR;
+
+  cur_token = * index;
+
+  r = mailimf_optional_field_parse(message, length, &cur_token,
+                                  &optional_field);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  field = mailimf_field_new(MAILIMF_FIELD_OPTIONAL_FIELD, NULL,
+      NULL, NULL, NULL,
+      NULL, NULL, NULL,
+      NULL, NULL, NULL,
+      NULL, NULL, NULL,
+      NULL, NULL, NULL, NULL, NULL,
+      NULL, NULL, NULL, optional_field);
+  if (field == NULL) {
+    mailimf_optional_field_free(optional_field);
+    return MAILIMF_ERROR_MEMORY;
+  }
+
+  * result = field;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+int
+mailimf_envelope_and_optional_fields_parse(const char * message, size_t length,
+                                          size_t * index,
+                                          struct mailimf_fields ** result)
+{
+  size_t cur_token;
+  clist * list;
+  struct mailimf_fields * fields;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  list = NULL;
+
+  r = mailimf_struct_multiple_parse(message, length, &cur_token,
+                                   &list,
+                                   (mailimf_struct_parser *)
+                                   mailimf_envelope_or_optional_field_parse,
+                                   (mailimf_struct_destructor *)
+                                   mailimf_field_free);
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    /* do nothing */
+    break;
+
+  case MAILIMF_ERROR_PARSE:
+    list = clist_new();
+    if (list == NULL) {
+      res = MAILIMF_ERROR_MEMORY;
+      goto err;
+    }
+    break;
+
+  default:
+    res = r;
+    goto err;
+  }
+
+  fields = mailimf_fields_new(list);
+  if (fields == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free;
+  }
+
+  * result = fields;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free:
+  if (list != NULL) {
+    clist_foreach(list, (clist_func) mailimf_field_free, NULL);
+    clist_free(list);
+  }
+ err:
+  return res;
+}
+
+
+
+static int
+mailimf_only_optional_field_parse(const char * message,
+                                 size_t length,
+                                 size_t * index,
+                                 struct mailimf_field ** result)
+{
+  int r;
+  size_t cur_token;
+  struct mailimf_optional_field * optional_field;
+  struct mailimf_field * field;
+
+  cur_token = * index;
+
+  r = mailimf_optional_field_parse(message, length, &cur_token,
+                                  &optional_field);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  field = mailimf_field_new(MAILIMF_FIELD_OPTIONAL_FIELD, NULL, NULL, NULL,
+      NULL, NULL, NULL, NULL, NULL,
+      NULL, NULL, NULL, NULL, NULL,
+      NULL, NULL, NULL, NULL, NULL,
+      NULL, NULL, NULL, optional_field);
+  if (field == NULL) {
+    mailimf_optional_field_free(optional_field);
+    return MAILIMF_ERROR_MEMORY;
+  }
+
+  * result = field;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+int
+mailimf_optional_fields_parse(const char * message, size_t length,
+                             size_t * index,
+                             struct mailimf_fields ** result)
+{
+  size_t cur_token;
+  clist * list;
+  struct mailimf_fields * fields;
+  int r;
+  int res;
+
+  cur_token = * index;
+
+  list = NULL;
+
+  r = mailimf_struct_multiple_parse(message, length, &cur_token,
+                                   &list,
+                                   (mailimf_struct_parser *)
+                                   mailimf_only_optional_field_parse,
+                                   (mailimf_struct_destructor *)
+                                   mailimf_field_free);
+  switch (r) {
+  case MAILIMF_NO_ERROR:
+    /* do nothing */
+    break;
+
+  case MAILIMF_ERROR_PARSE:
+    list = clist_new();
+    if (list == NULL) {
+      res = MAILIMF_ERROR_MEMORY;
+      goto err;
+    }
+    break;
+
+  default:
+    res = r;
+    goto err;
+  }
+
+  fields = mailimf_fields_new(list);
+  if (fields == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free;
+  }
+
+  * result = fields;
+  * index = cur_token;
+
+  return MAILIMF_NO_ERROR;
+
+ free:
+  if (list != NULL) {
+    clist_foreach(list, (clist_func) mailimf_field_free, NULL);
+    clist_free(list);
+  }
+ err:
+  return res;
+}
diff --git a/src/plugins/mailmbox/mailimf.h b/src/plugins/mailmbox/mailimf.h
new file mode 100644 (file)
index 0000000..9fef567
--- /dev/null
@@ -0,0 +1,345 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef MAILIMF_H
+
+#define MAILIMF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "mailimf_types.h"
+#include "mailimf_write.h"
+#include "mailimf_types_helper.h"
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+/*
+  mailimf_message_parse will parse the given message
+  
+  @param message this is a string containing the message content
+  @param length this is the size of the given string
+  @param index this is a pointer to the start of the message in
+    the given string, (* index) is modified to point at the end
+    of the parsed data
+  @param result the result of the parse operation is stored in
+    (* result)
+
+  @return MAILIMF_NO_ERROR on success, MAILIMF_ERROR_XXX on error
+*/
+
+int mailimf_message_parse(const char * message, size_t length,
+                         size_t * index,
+                         struct mailimf_message ** result);
+
+/*
+  mailimf_body_parse will parse the given text part of a message
+  
+  @param message this is a string containing the message text part
+  @param length this is the size of the given string
+  @param index this is a pointer to the start of the message text part in
+    the given string, (* index) is modified to point at the end
+    of the parsed data
+  @param result the result of the parse operation is stored in
+    (* result)
+
+  @return MAILIMF_NO_ERROR on success, MAILIMF_ERROR_XXX on error
+*/
+
+int mailimf_body_parse(const char * message, size_t length,
+                      size_t * index,
+                      struct mailimf_body ** result);
+
+/*
+  mailimf_fields_parse will parse the given header fields
+  
+  @param message this is a string containing the header fields
+  @param length this is the size of the given string
+  @param index this is a pointer to the start of the header fields in
+    the given string, (* index) is modified to point at the end
+    of the parsed data
+  @param result the result of the parse operation is stored in
+    (* result)
+
+  @return MAILIMF_NO_ERROR on success, MAILIMF_ERROR_XXX on error
+*/
+
+int mailimf_fields_parse(const char * message, size_t length,
+                        size_t * index,
+                        struct mailimf_fields ** result);
+
+/*
+  mailimf_mailbox_list_parse will parse the given mailbox list
+  
+  @param message this is a string containing the mailbox list
+  @param length this is the size of the given string
+  @param index this is a pointer to the start of the mailbox list in
+    the given string, (* index) is modified to point at the end
+    of the parsed data
+  @param result the result of the parse operation is stored in
+    (* result)
+
+  @return MAILIMF_NO_ERROR on success, MAILIMF_ERROR_XXX on error
+*/
+
+int
+mailimf_mailbox_list_parse(const char * message, size_t length,
+                          size_t * index,
+                          struct mailimf_mailbox_list ** result);
+
+/*
+  mailimf_address_list_parse will parse the given address list
+  
+  @param message this is a string containing the address list
+  @param length this is the size of the given string
+  @param index this is a pointer to the start of the address list in
+    the given string, (* index) is modified to point at the end
+    of the parsed data
+  @param result the result of the parse operation is stored in
+    (* result)
+
+  @return MAILIMF_NO_ERROR on success, MAILIMF_ERROR_XXX on error
+*/
+
+int
+mailimf_address_list_parse(const char * message, size_t length,
+                          size_t * index,
+                          struct mailimf_address_list ** result);
+
+/*
+  mailimf_address_parse will parse the given address
+  
+  @param message this is a string containing the address
+  @param length this is the size of the given string
+  @param index this is a pointer to the start of the address in
+    the given string, (* index) is modified to point at the end
+    of the parsed data
+  @param result the result of the parse operation is stored in
+    (* result)
+
+  @return MAILIMF_NO_ERROR on success, MAILIMF_ERROR_XXX on error
+*/
+
+int mailimf_address_parse(const char * message, size_t length,
+                         size_t * index,
+                         struct mailimf_address ** result);
+
+/*
+  mailimf_mailbox_parse will parse the given address
+  
+  @param message this is a string containing the mailbox
+  @param length this is the size of the given string
+  @param index this is a pointer to the start of the mailbox in
+    the given string, (* index) is modified to point at the end
+    of the parsed data
+  @param result the result of the parse operation is stored in
+    (* result)
+
+  @return MAILIMF_NO_ERROR on success, MAILIMF_ERROR_XXX on error
+*/
+
+int mailimf_mailbox_parse(const char * message, size_t length,
+                         size_t * index,
+                         struct mailimf_mailbox ** result);
+
+/*
+  mailimf_date_time_parse will parse the given RFC 2822 date
+  
+  @param message this is a string containing the date
+  @param length this is the size of the given string
+  @param index this is a pointer to the start of the date in
+    the given string, (* index) is modified to point at the end
+    of the parsed data
+  @param result the result of the parse operation is stored in
+    (* result)
+
+  @return MAILIMF_NO_ERROR on success, MAILIMF_ERROR_XXX on error
+*/
+
+int mailimf_date_time_parse(const char * message, size_t length,
+                           size_t * index,
+                           struct mailimf_date_time ** result);
+
+/*
+  mailimf_envelope_fields_parse will parse the given fields (Date,
+  From, Sender, Reply-To, To, Cc, Bcc, Message-ID, In-Reply-To,
+  References and Subject)
+  
+  @param message this is a string containing the header fields
+  @param length this is the size of the given string
+  @param index this is a pointer to the start of the header fields in
+    the given string, (* index) is modified to point at the end
+    of the parsed data
+  @param result the result of the parse operation is stored in
+    (* result)
+
+  @return MAILIMF_NO_ERROR on success, MAILIMF_ERROR_XXX on error
+*/
+
+int mailimf_envelope_fields_parse(const char * message, size_t length,
+                                 size_t * index,
+                                 struct mailimf_fields ** result);
+
+/*
+  mailimf_ignore_field_parse will skip the given field
+  
+  @param message this is a string containing the header field
+  @param length this is the size of the given string
+  @param index this is a pointer to the start of the header field in
+    the given string, (* index) is modified to point at the end
+    of the parsed data
+
+  @return MAILIMF_NO_ERROR on success, MAILIMF_ERROR_XXX on error
+*/
+
+
+int mailimf_ignore_field_parse(const char * message, size_t length,
+                              size_t * index);
+
+/*
+  mailimf_envelope_fields will parse the given fields (Date,
+  From, Sender, Reply-To, To, Cc, Bcc, Message-ID, In-Reply-To,
+  References and Subject), other fields will be added as optional
+  fields.
+  
+  @param message this is a string containing the header fields
+  @param length this is the size of the given string
+  @param index this is a pointer to the start of the header fields in
+    the given string, (* index) is modified to point at the end
+    of the parsed data
+  @param result the result of the parse operation is stored in
+    (* result)
+
+  @return MAILIMF_NO_ERROR on success, MAILIMF_ERROR_XXX on error
+*/
+
+
+int
+mailimf_envelope_and_optional_fields_parse(const char * message, size_t length,
+                                          size_t * index,
+                                          struct mailimf_fields ** result);
+
+/*
+  mailimf_envelope_fields will parse the given fields as optional
+  fields.
+  
+  @param message this is a string containing the header fields
+  @param length this is the size of the given string
+  @param index this is a pointer to the start of the header fields in
+    the given string, (* index) is modified to point at the end
+    of the parsed data
+  @param result the result of the parse operation is stored in
+    (* result)
+
+  @return MAILIMF_NO_ERROR on success, MAILIMF_ERROR_XXX on error
+*/
+
+int
+mailimf_optional_fields_parse(const char * message, size_t length,
+                             size_t * index,
+                             struct mailimf_fields ** result);
+
+
+/* internal use, exported for MIME */
+
+int mailimf_fws_parse(const char * message, size_t length, size_t * index);
+
+int mailimf_cfws_parse(const char * message, size_t length,
+                      size_t * index);
+
+int mailimf_char_parse(const char * message, size_t length,
+                      size_t * index, char token);
+
+int mailimf_unstrict_char_parse(const char * message, size_t length,
+                               size_t * index, char token);
+
+int mailimf_crlf_parse(const char * message, size_t length, size_t * index);
+
+int
+mailimf_custom_string_parse(const char * message, size_t length,
+                           size_t * index, char ** result,
+                           int (* is_custom_char)(char));
+
+int
+mailimf_token_case_insensitive_len_parse(const char * message, size_t length,
+                                        size_t * index, char * token,
+                                        size_t token_length);
+
+#define mailimf_token_case_insensitive_parse(message, length, index, token) \
+    mailimf_token_case_insensitive_len_parse(message, length, index, token, \
+                                            sizeof(token) - 1)
+
+int mailimf_quoted_string_parse(const char * message, size_t length,
+                               size_t * index, char ** result);
+
+int
+mailimf_number_parse(const char * message, size_t length,
+                    size_t * index, uint32_t * result);
+
+int mailimf_msg_id_parse(const char * message, size_t length,
+                        size_t * index,
+                        char ** result);
+
+int mailimf_msg_id_list_parse(const char * message, size_t length,
+                             size_t * index, clist ** result);
+
+int mailimf_word_parse(const char * message, size_t length,
+                      size_t * index, char ** result);
+
+int mailimf_atom_parse(const char * message, size_t length,
+                      size_t * index, char ** result);
+
+int mailimf_fws_atom_parse(const char * message, size_t length,
+                          size_t * index, char ** result);
+
+int mailimf_fws_word_parse(const char * message, size_t length,
+                          size_t * index, char ** result);
+
+int mailimf_fws_quoted_string_parse(const char * message, size_t length,
+                                   size_t * index, char ** result);
+
+/* exported for IMAP */
+
+int mailimf_references_parse(const char * message, size_t length,
+                            size_t * index,
+                            struct mailimf_references ** result);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/plugins/mailmbox/mailimf_types.c b/src/plugins/mailmbox/mailimf_types.c
new file mode 100644 (file)
index 0000000..fbef8f0
--- /dev/null
@@ -0,0 +1,868 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#include "mailimf_types.h"
+#include "mmapstring.h"
+#include <stdlib.h>
+
+void mailimf_atom_free(char * atom)
+{
+  free(atom);
+}
+
+void mailimf_dot_atom_free(char * dot_atom)
+{
+  free(dot_atom);
+}
+
+void mailimf_dot_atom_text_free(char * dot_atom)
+{
+  free(dot_atom);
+}
+
+void mailimf_quoted_string_free(char * quoted_string)
+{
+  free(quoted_string);
+}
+
+void mailimf_word_free(char * word)
+{
+  free(word);
+}
+
+void mailimf_phrase_free(char * phrase)
+{
+  free(phrase);
+}
+
+void mailimf_unstructured_free(char * unstructured)
+{
+  free(unstructured);
+}
+
+
+struct mailimf_date_time *
+mailimf_date_time_new(int dt_day, int dt_month, int dt_year,
+    int dt_hour, int dt_min, int dt_sec, int dt_zone)
+{
+  struct mailimf_date_time * date_time;
+
+  date_time = malloc(sizeof(* date_time));
+  if (date_time == NULL)
+    return NULL;
+
+  date_time->dt_day = dt_day;
+  date_time->dt_month = dt_month;
+  date_time->dt_year = dt_year;
+  date_time->dt_hour = dt_hour;
+  date_time->dt_min = dt_min;
+  date_time->dt_sec = dt_sec;
+  date_time->dt_zone = dt_zone;
+
+  return date_time;
+}
+
+
+void mailimf_date_time_free(struct mailimf_date_time * date_time)
+{
+  free(date_time);
+}
+
+
+
+
+struct mailimf_address *
+mailimf_address_new(int ad_type, struct mailimf_mailbox * ad_mailbox,
+    struct mailimf_group * ad_group)
+{
+  struct mailimf_address * address;
+
+  address = malloc(sizeof(* address));
+  if (address == NULL)
+    return NULL;
+
+  address->ad_type = ad_type;
+  switch (ad_type) {
+  case MAILIMF_ADDRESS_MAILBOX:
+    address->ad_data.ad_mailbox = ad_mailbox;
+    break;
+  case MAILIMF_ADDRESS_GROUP:
+    address->ad_data.ad_group = ad_group;
+    break;
+  }
+
+  return address;
+}
+
+void mailimf_address_free(struct mailimf_address * address)
+{
+  switch (address->ad_type) {
+  case MAILIMF_ADDRESS_MAILBOX:
+    mailimf_mailbox_free(address->ad_data.ad_mailbox);
+    break;
+  case MAILIMF_ADDRESS_GROUP:
+    mailimf_group_free(address->ad_data.ad_group);
+  }
+  free(address);
+}
+
+struct mailimf_mailbox *
+mailimf_mailbox_new(char * mb_display_name, char * mb_addr_spec)
+{
+  struct mailimf_mailbox * mb;
+
+  mb = malloc(sizeof(* mb));
+  if (mb == NULL)
+    return NULL;
+
+  mb->mb_display_name = mb_display_name;
+  mb->mb_addr_spec = mb_addr_spec;
+
+  return mb;
+}
+
+void mailimf_mailbox_free(struct mailimf_mailbox * mailbox)
+{
+  if (mailbox->mb_display_name != NULL)
+    mailimf_display_name_free(mailbox->mb_display_name);
+  mailimf_addr_spec_free(mailbox->mb_addr_spec);
+  free(mailbox);
+}
+
+
+void mailimf_angle_addr_free(char * angle_addr)
+{
+  free(angle_addr);
+}
+
+
+struct mailimf_group *
+mailimf_group_new(char * grp_display_name,
+    struct mailimf_mailbox_list * grp_mb_list)
+{
+  struct mailimf_group * group;
+
+  group = malloc(sizeof(* group));
+  if (group == NULL)
+    return NULL;
+
+  group->grp_display_name = grp_display_name;
+  group->grp_mb_list = grp_mb_list;
+  
+  return group;
+}
+
+void mailimf_group_free(struct mailimf_group * group)
+{
+  if (group->grp_mb_list)
+    mailimf_mailbox_list_free(group->grp_mb_list);
+  mailimf_display_name_free(group->grp_display_name);
+  free(group);
+}
+
+void mailimf_display_name_free(char * display_name)
+{
+  mailimf_phrase_free(display_name);
+}
+
+
+struct mailimf_mailbox_list *
+mailimf_mailbox_list_new(clist * mb_list)
+{
+  struct mailimf_mailbox_list * mbl;
+
+  mbl = malloc(sizeof(* mbl));
+  if (mbl == NULL)
+    return NULL;
+
+  mbl->mb_list = mb_list;
+
+  return mbl;
+}
+
+void mailimf_mailbox_list_free(struct mailimf_mailbox_list * mb_list)
+{
+  clist_foreach(mb_list->mb_list, (clist_func) mailimf_mailbox_free, NULL);
+  clist_free(mb_list->mb_list);
+  free(mb_list);
+}
+
+
+
+struct mailimf_address_list *
+mailimf_address_list_new(clist * ad_list)
+{
+  struct mailimf_address_list * addr_list;
+
+  addr_list = malloc(sizeof(* addr_list));
+  if (addr_list == NULL)
+    return NULL;
+
+  addr_list->ad_list = ad_list;
+
+  return addr_list;
+}
+
+void mailimf_address_list_free(struct mailimf_address_list * addr_list)
+{
+  clist_foreach(addr_list->ad_list, (clist_func) mailimf_address_free, NULL);
+  clist_free(addr_list->ad_list);
+  free(addr_list);
+}
+
+
+void mailimf_addr_spec_free(char * addr_spec)
+{
+  free(addr_spec);
+}
+
+void mailimf_local_part_free(char * local_part)
+{
+  free(local_part);
+}
+
+void mailimf_domain_free(char * domain)
+{
+  free(domain);
+}
+
+void mailimf_domain_literal_free(char * domain_literal)
+{
+  free(domain_literal);
+}
+
+
+
+struct mailimf_message * 
+mailimf_message_new(struct mailimf_fields * msg_fields,
+    struct mailimf_body * msg_body)
+{
+  struct mailimf_message * message;
+  
+  message = malloc(sizeof(* message));
+  if (message == NULL)
+    return NULL;
+
+  message->msg_fields = msg_fields;
+  message->msg_body = msg_body;
+
+  return message;
+}
+
+void mailimf_message_free(struct mailimf_message * message)
+{
+  mailimf_body_free(message->msg_body);
+  mailimf_fields_free(message->msg_fields);
+  free(message);
+}
+
+
+struct mailimf_body * mailimf_body_new(const char * bd_text, size_t bd_size)
+{
+  struct mailimf_body * body;
+
+  body = malloc(sizeof(* body));
+  if (body == NULL)
+    return NULL;
+  body->bd_text = bd_text;
+  body->bd_size = bd_size;
+
+  return body;
+}
+
+void mailimf_body_free(struct mailimf_body * body)
+{
+  free(body);
+}
+
+
+
+struct mailimf_field *
+mailimf_field_new(int fld_type,
+    struct mailimf_return * fld_return_path,
+    struct mailimf_orig_date * fld_resent_date,
+    struct mailimf_from * fld_resent_from,
+    struct mailimf_sender * fld_resent_sender,
+    struct mailimf_to * fld_resent_to,
+    struct mailimf_cc * fld_resent_cc,
+    struct mailimf_bcc * fld_resent_bcc,
+    struct mailimf_message_id * fld_resent_msg_id,
+    struct mailimf_orig_date * fld_orig_date,
+    struct mailimf_from * fld_from,
+    struct mailimf_sender * fld_sender,
+    struct mailimf_reply_to * fld_reply_to,
+    struct mailimf_to * fld_to,
+    struct mailimf_cc * fld_cc,
+    struct mailimf_bcc * fld_bcc,
+    struct mailimf_message_id * fld_message_id,
+    struct mailimf_in_reply_to * fld_in_reply_to,
+    struct mailimf_references * fld_references,
+    struct mailimf_subject * fld_subject,
+    struct mailimf_comments * fld_comments,
+    struct mailimf_keywords * fld_keywords,
+    struct mailimf_optional_field * fld_optional_field)
+{
+  struct mailimf_field * field;
+
+  field = malloc(sizeof(* field));
+  if (field == NULL)
+    return NULL;
+
+  field->fld_type = fld_type;
+  switch (fld_type) {
+  case MAILIMF_FIELD_RETURN_PATH:
+    field->fld_data.fld_return_path = fld_return_path;
+    break;
+  case MAILIMF_FIELD_RESENT_DATE:
+    field->fld_data.fld_resent_date = fld_resent_date;
+    break;
+  case MAILIMF_FIELD_RESENT_FROM:
+    field->fld_data.fld_resent_from = fld_resent_from;
+    break;
+  case MAILIMF_FIELD_RESENT_SENDER:
+    field->fld_data.fld_resent_sender = fld_resent_sender;
+    break;
+  case MAILIMF_FIELD_RESENT_TO:
+    field->fld_data.fld_resent_to = fld_resent_to;
+    break;
+  case MAILIMF_FIELD_RESENT_CC:
+    field->fld_data.fld_resent_cc = fld_resent_cc;
+    break;
+  case MAILIMF_FIELD_RESENT_BCC:
+    field->fld_data.fld_resent_bcc = fld_resent_bcc;
+    break;
+  case MAILIMF_FIELD_RESENT_MSG_ID:
+    field->fld_data.fld_resent_msg_id = fld_resent_msg_id;
+    break;
+  case MAILIMF_FIELD_ORIG_DATE:
+    field->fld_data.fld_orig_date = fld_orig_date;
+    break;
+  case MAILIMF_FIELD_FROM:
+    field->fld_data.fld_from = fld_from;
+    break;
+  case MAILIMF_FIELD_SENDER:
+    field->fld_data.fld_sender = fld_sender;
+    break;
+  case MAILIMF_FIELD_REPLY_TO:
+    field->fld_data.fld_reply_to = fld_reply_to;
+    break;
+  case MAILIMF_FIELD_TO:
+    field->fld_data.fld_to = fld_to;
+    break;
+  case MAILIMF_FIELD_CC:
+    field->fld_data.fld_cc = fld_cc;
+    break;
+  case MAILIMF_FIELD_BCC:
+    field->fld_data.fld_bcc = fld_bcc;
+    break;
+  case MAILIMF_FIELD_MESSAGE_ID:
+    field->fld_data.fld_message_id = fld_message_id;
+    break;
+  case MAILIMF_FIELD_IN_REPLY_TO:
+    field->fld_data.fld_in_reply_to = fld_in_reply_to;
+    break;
+  case MAILIMF_FIELD_REFERENCES:
+    field->fld_data.fld_references = fld_references;
+    break;
+  case MAILIMF_FIELD_SUBJECT:
+    field->fld_data.fld_subject = fld_subject;
+    break;
+  case MAILIMF_FIELD_COMMENTS:
+    field->fld_data.fld_comments = fld_comments;
+    break;
+  case MAILIMF_FIELD_KEYWORDS:
+    field->fld_data.fld_keywords = fld_keywords;
+    break;
+  case MAILIMF_FIELD_OPTIONAL_FIELD:
+    field->fld_data.fld_optional_field = fld_optional_field;
+    break;
+  }
+  
+  return field;
+}
+
+void mailimf_field_free(struct mailimf_field * field)
+{
+  switch (field->fld_type) {
+  case MAILIMF_FIELD_RETURN_PATH:
+    mailimf_return_free(field->fld_data.fld_return_path);
+    break;
+  case MAILIMF_FIELD_RESENT_DATE:
+    mailimf_orig_date_free(field->fld_data.fld_resent_date);
+    break;
+  case MAILIMF_FIELD_RESENT_FROM:
+    mailimf_from_free(field->fld_data.fld_resent_from);
+    break;
+  case MAILIMF_FIELD_RESENT_SENDER:
+    mailimf_sender_free(field->fld_data.fld_resent_sender);
+    break;
+  case MAILIMF_FIELD_RESENT_TO:
+    mailimf_to_free(field->fld_data.fld_resent_to);
+    break;
+  case MAILIMF_FIELD_RESENT_CC:
+    mailimf_cc_free(field->fld_data.fld_resent_cc);
+    break;
+  case MAILIMF_FIELD_RESENT_BCC:
+    mailimf_bcc_free(field->fld_data.fld_resent_bcc);
+    break;
+  case MAILIMF_FIELD_RESENT_MSG_ID:
+    mailimf_message_id_free(field->fld_data.fld_resent_msg_id);
+    break;
+  case MAILIMF_FIELD_ORIG_DATE:
+    mailimf_orig_date_free(field->fld_data.fld_orig_date);
+    break;
+  case MAILIMF_FIELD_FROM:
+    mailimf_from_free(field->fld_data.fld_from);
+    break;
+  case MAILIMF_FIELD_SENDER:
+    mailimf_sender_free(field->fld_data.fld_sender);
+    break;
+  case MAILIMF_FIELD_REPLY_TO:
+    mailimf_reply_to_free(field->fld_data.fld_reply_to);
+    break;
+  case MAILIMF_FIELD_TO:
+    mailimf_to_free(field->fld_data.fld_to);
+    break;
+  case MAILIMF_FIELD_CC:
+    mailimf_cc_free(field->fld_data.fld_cc);
+    break;
+  case MAILIMF_FIELD_BCC:
+    mailimf_bcc_free(field->fld_data.fld_bcc);
+    break;
+  case MAILIMF_FIELD_MESSAGE_ID:
+    mailimf_message_id_free(field->fld_data.fld_message_id);
+    break;
+  case MAILIMF_FIELD_IN_REPLY_TO:
+    mailimf_in_reply_to_free(field->fld_data.fld_in_reply_to);
+    break;
+  case MAILIMF_FIELD_REFERENCES:
+    mailimf_references_free(field->fld_data.fld_references);
+    break;
+  case MAILIMF_FIELD_SUBJECT:
+    mailimf_subject_free(field->fld_data.fld_subject);
+    break;
+  case MAILIMF_FIELD_COMMENTS:
+    mailimf_comments_free(field->fld_data.fld_comments);
+    break;
+  case MAILIMF_FIELD_KEYWORDS:
+    mailimf_keywords_free(field->fld_data.fld_keywords);
+    break;
+  case MAILIMF_FIELD_OPTIONAL_FIELD:
+    mailimf_optional_field_free(field->fld_data.fld_optional_field);
+    break;
+  }
+  
+  free(field);
+}
+
+struct mailimf_fields * mailimf_fields_new(clist * fld_list)
+{
+  struct mailimf_fields * fields;
+
+  fields = malloc(sizeof(* fields));
+  if (fields == NULL)
+    return NULL;
+
+  fields->fld_list = fld_list;
+
+  return fields;
+}
+
+void mailimf_fields_free(struct mailimf_fields * fields)
+{
+  if (fields->fld_list != NULL) {
+    clist_foreach(fields->fld_list, (clist_func) mailimf_field_free, NULL);
+    clist_free(fields->fld_list);
+  }
+  free(fields);
+}
+
+
+struct mailimf_orig_date * mailimf_orig_date_new(struct mailimf_date_time *
+    dt_date_time)
+{
+  struct mailimf_orig_date * orig_date;
+
+  orig_date = malloc(sizeof(* orig_date));
+  if (orig_date == NULL)
+    return NULL;
+
+  orig_date->dt_date_time = dt_date_time;
+
+  return orig_date;
+}
+
+void mailimf_orig_date_free(struct mailimf_orig_date * orig_date)
+{
+  if (orig_date->dt_date_time != NULL)
+    mailimf_date_time_free(orig_date->dt_date_time);
+  free(orig_date);
+}
+
+struct mailimf_from *
+mailimf_from_new(struct mailimf_mailbox_list * frm_mb_list)
+{
+  struct mailimf_from * from;
+
+  from = malloc(sizeof(* from));
+  if (from == NULL)
+    return NULL;
+  
+  from->frm_mb_list = frm_mb_list;
+
+  return from;
+}
+
+void mailimf_from_free(struct mailimf_from * from)
+{
+  if (from->frm_mb_list != NULL)
+    mailimf_mailbox_list_free(from->frm_mb_list);
+  free(from);
+}
+
+struct mailimf_sender * mailimf_sender_new(struct mailimf_mailbox * snd_mb)
+{
+  struct mailimf_sender * sender;
+
+  sender = malloc(sizeof(* sender));
+  if (sender == NULL)
+    return NULL;
+
+  sender->snd_mb = snd_mb;
+
+  return sender;
+}
+
+void mailimf_sender_free(struct mailimf_sender * sender)
+{
+  if (sender->snd_mb != NULL)
+    mailimf_mailbox_free(sender->snd_mb);
+  free(sender);
+}
+
+struct mailimf_reply_to *
+mailimf_reply_to_new(struct mailimf_address_list * rt_addr_list)
+{
+  struct mailimf_reply_to * reply_to;
+
+  reply_to = malloc(sizeof(* reply_to));
+  if (reply_to == NULL)
+    return NULL;
+
+  reply_to->rt_addr_list = rt_addr_list;
+
+  return reply_to;
+}
+
+void mailimf_reply_to_free(struct mailimf_reply_to * reply_to)
+{
+  if (reply_to->rt_addr_list != NULL)
+    mailimf_address_list_free(reply_to->rt_addr_list);
+  free(reply_to);
+}
+
+struct mailimf_to * mailimf_to_new(struct mailimf_address_list * to_addr_list)
+{
+  struct mailimf_to * to;
+
+  to = malloc(sizeof(* to));
+  if (to == NULL)
+    return NULL;
+
+  to->to_addr_list = to_addr_list;
+
+  return to;
+}
+
+void mailimf_to_free(struct mailimf_to * to)
+{
+  if (to->to_addr_list != NULL)
+    mailimf_address_list_free(to->to_addr_list);
+  free(to);
+}
+
+struct mailimf_cc * mailimf_cc_new(struct mailimf_address_list * cc_addr_list)
+{
+  struct mailimf_cc * cc;
+
+  cc = malloc(sizeof(* cc));
+  if (cc == NULL)
+    return NULL;
+
+  cc->cc_addr_list = cc_addr_list;
+
+  return cc;
+}
+
+void mailimf_cc_free(struct mailimf_cc * cc)
+{
+  if (cc->cc_addr_list != NULL)
+    mailimf_address_list_free(cc->cc_addr_list);
+  free(cc);
+}
+
+struct mailimf_bcc *
+mailimf_bcc_new(struct mailimf_address_list * bcc_addr_list)
+{
+  struct mailimf_bcc * bcc;
+
+  bcc = malloc(sizeof(* bcc));
+  if (bcc == NULL)
+    return NULL;
+
+  bcc->bcc_addr_list = bcc_addr_list;
+
+  return bcc;
+}
+
+void mailimf_bcc_free(struct mailimf_bcc * bcc)
+{
+  if (bcc->bcc_addr_list != NULL)
+    mailimf_address_list_free(bcc->bcc_addr_list);
+  free(bcc);
+}
+
+struct mailimf_message_id * mailimf_message_id_new(char * mid_value)
+{
+  struct mailimf_message_id * message_id;
+
+  message_id = malloc(sizeof(* message_id));
+  if (message_id == NULL)
+    return NULL;
+
+  message_id->mid_value = mid_value;
+
+  return message_id;
+}
+
+void mailimf_message_id_free(struct mailimf_message_id * message_id)
+{
+  if (message_id->mid_value != NULL)
+    mailimf_msg_id_free(message_id->mid_value);
+  free(message_id);
+}
+
+struct mailimf_in_reply_to * mailimf_in_reply_to_new(clist * mid_list)
+{
+  struct mailimf_in_reply_to * in_reply_to;
+
+  in_reply_to = malloc(sizeof(* in_reply_to));
+  if (in_reply_to == NULL)
+    return NULL;
+
+  in_reply_to->mid_list = mid_list;
+
+  return in_reply_to;
+}
+
+void mailimf_in_reply_to_free(struct mailimf_in_reply_to * in_reply_to)
+{
+  clist_foreach(in_reply_to->mid_list,
+               (clist_func) mailimf_msg_id_free, NULL);
+  clist_free(in_reply_to->mid_list);
+  free(in_reply_to);
+}
+
+struct mailimf_references * mailimf_references_new(clist * mid_list)
+{
+  struct mailimf_references * ref;
+
+  ref = malloc(sizeof(* ref));
+  if (ref == NULL)
+    return NULL;
+
+  ref->mid_list = mid_list;
+
+  return ref;
+}
+
+void mailimf_references_free(struct mailimf_references * references)
+{
+  clist_foreach(references->mid_list,
+      (clist_func) mailimf_msg_id_free, NULL);
+  clist_free(references->mid_list);
+  free(references);
+}
+
+void mailimf_msg_id_free(char * msg_id)
+{
+  free(msg_id);
+}
+
+void mailimf_id_left_free(char * id_left)
+{
+  free(id_left);
+}
+
+void mailimf_id_right_free(char * id_right)
+{
+  free(id_right);
+}
+
+void mailimf_no_fold_quote_free(char * nfq)
+{
+  free(nfq);
+}
+
+void mailimf_no_fold_literal_free(char * nfl)
+{
+  free(nfl);
+}
+
+struct mailimf_subject * mailimf_subject_new(char * sbj_value)
+{
+  struct mailimf_subject * subject;
+
+  subject = malloc(sizeof(* subject));
+  if (subject == NULL)
+    return NULL;
+
+  subject->sbj_value = sbj_value;
+
+  return subject;
+}
+
+void mailimf_subject_free(struct mailimf_subject * subject)
+{
+  mailimf_unstructured_free(subject->sbj_value);
+  free(subject);
+}
+
+struct mailimf_comments * mailimf_comments_new(char * cm_value)
+{
+  struct mailimf_comments * comments;
+
+  comments = malloc(sizeof(* comments));
+  if (comments == NULL)
+    return NULL;
+
+  comments->cm_value = cm_value;
+
+  return comments;
+}
+
+void mailimf_comments_free(struct mailimf_comments * comments)
+{
+  mailimf_unstructured_free(comments->cm_value);
+  free(comments);
+}
+
+struct mailimf_keywords * mailimf_keywords_new(clist * kw_list)
+{
+  struct mailimf_keywords * keywords;
+
+  keywords = malloc(sizeof(* keywords));
+  if (keywords == NULL)
+    return NULL;
+
+  keywords->kw_list = kw_list;
+  
+  return keywords;
+}
+
+void mailimf_keywords_free(struct mailimf_keywords * keywords)
+{
+  clist_foreach(keywords->kw_list, (clist_func) mailimf_phrase_free, NULL);
+  clist_free(keywords->kw_list);
+  free(keywords);
+}
+
+struct mailimf_return *
+mailimf_return_new(struct mailimf_path * ret_path)
+{
+  struct mailimf_return * return_path;
+
+  return_path = malloc(sizeof(* return_path));
+  if (return_path == NULL)
+    return NULL;
+
+  return_path->ret_path = ret_path;
+
+  return return_path;
+}
+
+void mailimf_return_free(struct mailimf_return * return_path)
+{
+  mailimf_path_free(return_path->ret_path);
+  free(return_path);
+}
+
+
+struct mailimf_path * mailimf_path_new(char * pt_addr_spec)
+{
+  struct mailimf_path * path;
+
+  path = malloc(sizeof(* path));
+  if (path == NULL)
+    return NULL;
+
+  path->pt_addr_spec = pt_addr_spec;
+
+  return path;
+}
+
+void mailimf_path_free(struct mailimf_path * path)
+{
+  if (path->pt_addr_spec != NULL)
+    mailimf_addr_spec_free(path->pt_addr_spec);
+  free(path);
+}
+
+struct mailimf_optional_field *
+mailimf_optional_field_new(char * fld_name, char * fld_value)
+{
+  struct mailimf_optional_field * opt_field;
+
+  opt_field = malloc(sizeof(* opt_field));
+  if (opt_field == NULL)
+    return NULL;
+  
+  opt_field->fld_name = fld_name;
+  opt_field->fld_value = fld_value;
+
+  return opt_field;
+}
+
+void mailimf_optional_field_free(struct mailimf_optional_field * opt_field)
+{
+  mailimf_field_name_free(opt_field->fld_name);
+  mailimf_unstructured_free(opt_field->fld_value);
+  free(opt_field);
+}
+
+void mailimf_field_name_free(char * field_name)
+{
+  free(field_name);
+}
diff --git a/src/plugins/mailmbox/mailimf_types.h b/src/plugins/mailmbox/mailimf_types.h
new file mode 100644 (file)
index 0000000..b62f494
--- /dev/null
@@ -0,0 +1,797 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001 - 2003 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+/*
+ * $Id$
+ */
+
+#ifndef MAILIMF_TYPES_H
+
+#define MAILIMF_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "clist.h"
+#include <sys/types.h>
+
+/*
+  IMPORTANT NOTE:
+  
+  All allocation functions will take as argument allocated data
+  and will store these data in the structure they will allocate.
+  Data should be persistant during all the use of the structure
+  and will be freed by the free function of the structure
+
+  allocation functions will return NULL on failure
+*/
+
+/*
+  mailimf_date_time is a date
+  
+  - day is the day of month (1 to 31)
+
+  - month (1 to 12)
+
+  - year (4 digits)
+
+  - hour (0 to 23)
+
+  - min (0 to 59)
+
+  - sec (0 to 59)
+
+  - zone (this is the decimal value that we can read, for example:
+    for "-0200", the value is -200)
+*/
+
+struct mailimf_date_time {
+  int dt_day;
+  int dt_month;
+  int dt_year;
+  int dt_hour;
+  int dt_min;
+  int dt_sec;
+  int dt_zone;
+};
+
+struct mailimf_date_time *
+mailimf_date_time_new(int dt_day, int dt_month, int dt_year,
+    int dt_hour, int dt_min, int dt_sec, int dt_zone);
+
+void mailimf_date_time_free(struct mailimf_date_time * date_time);
+
+
+
+/* this is the type of address */
+
+enum {
+  MAILIMF_ADDRESS_ERROR,   /* on parse error */
+  MAILIMF_ADDRESS_MAILBOX, /* if this is a mailbox (mailbox@domain) */
+  MAILIMF_ADDRESS_GROUP,   /* if this is a group
+                              (group_name: address1@domain1,
+                                  address2@domain2; ) */
+};
+
+/*
+  mailimf_address is an address
+
+  - type can be MAILIMF_ADDRESS_MAILBOX or MAILIMF_ADDRESS_GROUP
+
+  - mailbox is a mailbox if type is MAILIMF_ADDRESS_MAILBOX
+
+  - group is a group if type is MAILIMF_ADDRESS_GROUP
+*/
+
+struct mailimf_address {
+  int ad_type;
+  union {
+    struct mailimf_mailbox * ad_mailbox; /* can be NULL */
+    struct mailimf_group * ad_group;     /* can be NULL */
+  } ad_data;
+};
+
+
+struct mailimf_address *
+mailimf_address_new(int ad_type, struct mailimf_mailbox * ad_mailbox,
+    struct mailimf_group * ad_group);
+
+void mailimf_address_free(struct mailimf_address * address);
+
+
+
+/*
+  mailimf_mailbox is a mailbox
+
+  - display_name is the name that will be displayed for this mailbox,
+    for example 'name' in '"name" <mailbox@domain>,
+    should be allocated with malloc()
+  
+  - addr_spec is the mailbox, for example 'mailbox@domain'
+    in '"name" <mailbox@domain>, should be allocated with malloc()
+*/
+
+struct mailimf_mailbox {
+  char * mb_display_name; /* can be NULL */
+  char * mb_addr_spec;    /* != NULL */
+};
+
+struct mailimf_mailbox *
+mailimf_mailbox_new(char * mb_display_name, char * mb_addr_spec);
+
+void mailimf_mailbox_free(struct mailimf_mailbox * mailbox);
+
+
+
+/*
+  mailimf_group is a group
+
+  - display_name is the name that will be displayed for this group,
+    for example 'group_name' in
+    'group_name: address1@domain1, address2@domain2;', should be allocated
+    with malloc()
+
+  - mb_list is a list of mailboxes
+*/
+
+struct mailimf_group {
+  char * grp_display_name; /* != NULL */
+  struct mailimf_mailbox_list * grp_mb_list; /* can be NULL */
+};
+
+struct mailimf_group *
+mailimf_group_new(char * grp_display_name,
+    struct mailimf_mailbox_list * grp_mb_list);
+
+void mailimf_group_free(struct mailimf_group * group);
+
+
+
+/*
+  mailimf_mailbox_list is a list of mailboxes
+
+  - list is a list of mailboxes
+*/
+
+struct mailimf_mailbox_list {
+  clist * mb_list; /* list of (struct mailimf_mailbox *) */
+     /* != NULL */
+};
+
+struct mailimf_mailbox_list *
+mailimf_mailbox_list_new(clist * mb_list);
+
+void mailimf_mailbox_list_free(struct mailimf_mailbox_list * mb_list);
+
+
+
+/*
+  mailimf_address_list is a list of addresses
+
+  - list is a list of addresses
+*/
+
+struct mailimf_address_list {
+  clist * ad_list; /* list of (struct mailimf_address *) */
+     /* != NULL */
+};
+
+struct mailimf_address_list *
+mailimf_address_list_new(clist * ad_list);
+
+void mailimf_address_list_free(struct mailimf_address_list * addr_list);
+
+
+
+
+
+/*
+  mailimf_body is the text part of a message
+  
+  - text is the beginning of the text part, it is a substring
+    of an other string
+
+  - size is the size of the text part
+*/
+
+struct mailimf_body {
+  const char * bd_text; /* != NULL */
+  size_t bd_size;
+};
+
+struct mailimf_body * mailimf_body_new(const char * bd_text, size_t bd_size);
+
+void mailimf_body_free(struct mailimf_body * body);
+
+
+
+
+/*
+  mailimf_message is the content of the message
+
+  - fields is the header fields of the message
+  
+  - body is the text part of the message
+*/
+
+struct mailimf_message {
+  struct mailimf_fields * msg_fields; /* != NULL */
+  struct mailimf_body * msg_body;     /* != NULL */
+};
+
+struct mailimf_message *
+mailimf_message_new(struct mailimf_fields * msg_fields,
+    struct mailimf_body * msg_body);
+
+void mailimf_message_free(struct mailimf_message * message);
+
+
+
+
+/*
+  mailimf_fields is a list of header fields
+
+  - list is a list of header fields
+*/
+
+struct mailimf_fields {
+  clist * fld_list; /* list of (struct mailimf_field *) */
+                /* != NULL */
+};
+
+struct mailimf_fields * mailimf_fields_new(clist * fld_list);
+
+void mailimf_fields_free(struct mailimf_fields * fields);
+
+
+
+/* this is a type of field */
+
+enum {
+  MAILIMF_FIELD_NONE,           /* on parse error */
+  MAILIMF_FIELD_RETURN_PATH,    /* Return-Path */
+  MAILIMF_FIELD_RESENT_DATE,    /* Resent-Date */
+  MAILIMF_FIELD_RESENT_FROM,    /* Resent-From */
+  MAILIMF_FIELD_RESENT_SENDER,  /* Resent-Sender */
+  MAILIMF_FIELD_RESENT_TO,      /* Resent-To */
+  MAILIMF_FIELD_RESENT_CC,      /* Resent-Cc */
+  MAILIMF_FIELD_RESENT_BCC,     /* Resent-Bcc */
+  MAILIMF_FIELD_RESENT_MSG_ID,  /* Resent-Message-ID */
+  MAILIMF_FIELD_ORIG_DATE,      /* Date */
+  MAILIMF_FIELD_FROM,           /* From */
+  MAILIMF_FIELD_SENDER,         /* Sender */
+  MAILIMF_FIELD_REPLY_TO,       /* Reply-To */
+  MAILIMF_FIELD_TO,             /* To */
+  MAILIMF_FIELD_CC,             /* Cc */
+  MAILIMF_FIELD_BCC,            /* Bcc */
+  MAILIMF_FIELD_MESSAGE_ID,     /* Message-ID */
+  MAILIMF_FIELD_IN_REPLY_TO,    /* In-Reply-To */
+  MAILIMF_FIELD_REFERENCES,     /* References */
+  MAILIMF_FIELD_SUBJECT,        /* Subject */
+  MAILIMF_FIELD_COMMENTS,       /* Comments */
+  MAILIMF_FIELD_KEYWORDS,       /* Keywords */
+  MAILIMF_FIELD_OPTIONAL_FIELD, /* other field */
+};
+
+/*
+  mailimf_field is a field
+
+  - type is the type of the field
+
+  - return_path is the parsed content of the Return-Path field if type is
+    MAILIMF_FIELD_RETURN_PATH
+
+  - resent_date is the parsed content of the Resent-Date field if type is
+    MAILIMF_FIELD_RESENT_DATE
+
+  - resent_from is the parsed content of the Resent-From field
+
+  - resent_sender is the parsed content of the Resent-Sender field
+
+  - resent_to is the parsed content of the Resent-To field
+
+  - resent_cc is the parsed content of the Resent-Cc field
+
+  - resent_bcc is the parsed content of the Resent-Bcc field
+
+  - resent_msg_id is the parsed content of the Resent-Message-ID field
+
+  - orig_date is the parsed content of the Date field
+
+  - from is the parsed content of the From field
+
+  - sender is the parsed content of the Sender field
+
+  - reply_to is the parsed content of the Reply-To field
+
+  - to is the parsed content of the To field
+
+  - cc is the parsed content of the Cc field
+
+  - bcc is the parsed content of the Bcc field
+
+  - message_id is the parsed content of the Message-ID field
+
+  - in_reply_to is the parsed content of the In-Reply-To field
+
+  - references is the parsed content of the References field
+
+  - subject is the content of the Subject field
+
+  - comments is the content of the Comments field
+
+  - keywords is the parsed content of the Keywords field
+
+  - optional_field is an other field and is not parsed
+*/
+
+#define LIBETPAN_MAILIMF_FIELD_UNION
+
+struct mailimf_field {
+  int fld_type;
+  union {
+    struct mailimf_return * fld_return_path;              /* can be NULL */
+    struct mailimf_orig_date * fld_resent_date;    /* can be NULL */
+    struct mailimf_from * fld_resent_from;         /* can be NULL */
+    struct mailimf_sender * fld_resent_sender;     /* can be NULL */
+    struct mailimf_to * fld_resent_to;             /* can be NULL */
+    struct mailimf_cc * fld_resent_cc;             /* can be NULL */
+    struct mailimf_bcc * fld_resent_bcc;           /* can be NULL */
+    struct mailimf_message_id * fld_resent_msg_id; /* can be NULL */
+    struct mailimf_orig_date * fld_orig_date;             /* can be NULL */
+    struct mailimf_from * fld_from;                       /* can be NULL */
+    struct mailimf_sender * fld_sender;                   /* can be NULL */
+    struct mailimf_reply_to * fld_reply_to;               /* can be NULL */
+    struct mailimf_to * fld_to;                           /* can be NULL */
+    struct mailimf_cc * fld_cc;                           /* can be NULL */
+    struct mailimf_bcc * fld_bcc;                         /* can be NULL */
+    struct mailimf_message_id * fld_message_id;           /* can be NULL */
+    struct mailimf_in_reply_to * fld_in_reply_to;         /* can be NULL */
+    struct mailimf_references * fld_references;           /* can be NULL */
+    struct mailimf_subject * fld_subject;                 /* can be NULL */
+    struct mailimf_comments * fld_comments;               /* can be NULL */
+    struct mailimf_keywords * fld_keywords;               /* can be NULL */
+    struct mailimf_optional_field * fld_optional_field;   /* can be NULL */
+  } fld_data;
+};
+
+struct mailimf_field *
+mailimf_field_new(int fld_type,
+    struct mailimf_return * fld_return_path,
+    struct mailimf_orig_date * fld_resent_date,
+    struct mailimf_from * fld_resent_from,
+    struct mailimf_sender * fld_resent_sender,
+    struct mailimf_to * fld_resent_to,
+    struct mailimf_cc * fld_resent_cc,
+    struct mailimf_bcc * fld_resent_bcc,
+    struct mailimf_message_id * fld_resent_msg_id,
+    struct mailimf_orig_date * fld_orig_date,
+    struct mailimf_from * fld_from,
+    struct mailimf_sender * fld_sender,
+    struct mailimf_reply_to * fld_reply_to,
+    struct mailimf_to * fld_to,
+    struct mailimf_cc * fld_cc,
+    struct mailimf_bcc * fld_bcc,
+    struct mailimf_message_id * fld_message_id,
+    struct mailimf_in_reply_to * fld_in_reply_to,
+    struct mailimf_references * fld_references,
+    struct mailimf_subject * fld_subject,
+    struct mailimf_comments * fld_comments,
+    struct mailimf_keywords * fld_keywords,
+    struct mailimf_optional_field * fld_optional_field);
+
+void mailimf_field_free(struct mailimf_field * field);
+
+
+
+/*
+  mailimf_orig_date is the parsed Date field
+
+  - date_time is the parsed date
+*/
+
+struct mailimf_orig_date {
+  struct mailimf_date_time * dt_date_time; /* != NULL */
+};
+
+struct mailimf_orig_date * mailimf_orig_date_new(struct mailimf_date_time *
+    dt_date_time);
+
+void mailimf_orig_date_free(struct mailimf_orig_date * orig_date);
+
+
+
+
+/*
+  mailimf_from is the parsed From field
+
+  - mb_list is the parsed mailbox list
+*/
+
+struct mailimf_from {
+  struct mailimf_mailbox_list * frm_mb_list; /* != NULL */
+};
+
+struct mailimf_from *
+mailimf_from_new(struct mailimf_mailbox_list * frm_mb_list);
+
+void mailimf_from_free(struct mailimf_from * from);
+
+
+
+/*
+  mailimf_sender is the parsed Sender field
+
+  - mb is the parsed mailbox
+*/
+
+struct mailimf_sender {
+  struct mailimf_mailbox * snd_mb; /* != NULL */
+};
+
+struct mailimf_sender * mailimf_sender_new(struct mailimf_mailbox * snd_mb);
+
+void mailimf_sender_free(struct mailimf_sender * sender);
+
+
+
+
+/*
+  mailimf_reply_to is the parsed Reply-To field
+
+  - addr_list is the parsed address list
+ */
+
+struct mailimf_reply_to {
+  struct mailimf_address_list * rt_addr_list; /* != NULL */
+};
+
+struct mailimf_reply_to *
+mailimf_reply_to_new(struct mailimf_address_list * rt_addr_list);
+
+void mailimf_reply_to_free(struct mailimf_reply_to * reply_to);
+
+
+
+
+/*
+  mailimf_to is the parsed To field
+  
+  - addr_list is the parsed address list
+*/
+
+struct mailimf_to {
+  struct mailimf_address_list * to_addr_list; /* != NULL */
+};
+
+struct mailimf_to * mailimf_to_new(struct mailimf_address_list * to_addr_list);
+
+void mailimf_to_free(struct mailimf_to * to);
+
+
+
+
+/*
+  mailimf_cc is the parsed Cc field
+
+  - addr_list is the parsed addres list
+*/
+
+struct mailimf_cc {
+  struct mailimf_address_list * cc_addr_list; /* != NULL */
+};
+
+struct mailimf_cc * mailimf_cc_new(struct mailimf_address_list * cc_addr_list);
+
+void mailimf_cc_free(struct mailimf_cc * cc);
+
+
+
+
+/*
+  mailimf_bcc is the parsed Bcc field
+
+  - addr_list is the parsed addres list
+*/
+
+struct mailimf_bcc {
+  struct mailimf_address_list * bcc_addr_list; /* can be NULL */
+};
+
+struct mailimf_bcc *
+mailimf_bcc_new(struct mailimf_address_list * bcc_addr_list);
+
+void mailimf_bcc_free(struct mailimf_bcc * bcc);
+
+
+
+/*
+  mailimf_message_id is the parsed Message-ID field
+  
+  - value is the message identifier
+*/
+
+struct mailimf_message_id {
+  char * mid_value; /* != NULL */
+};
+
+struct mailimf_message_id * mailimf_message_id_new(char * mid_value);
+
+void mailimf_message_id_free(struct mailimf_message_id * message_id);
+
+
+
+
+/*
+  mailimf_in_reply_to is the parsed In-Reply-To field
+
+  - msg_id_list is the list of message identifers
+*/
+
+struct mailimf_in_reply_to {
+  clist * mid_list; /* list of (char *) */
+       /* != NULL */
+};
+
+struct mailimf_in_reply_to * mailimf_in_reply_to_new(clist * mid_list);
+
+void mailimf_in_reply_to_free(struct mailimf_in_reply_to * in_reply_to);
+
+
+
+/*
+  mailimf_references is the parsed References field
+
+  - msg_id_list is the list of message identifiers
+ */
+
+struct mailimf_references {
+  clist * mid_list; /* list of (char *) */
+       /* != NULL */
+};
+
+struct mailimf_references * mailimf_references_new(clist * mid_list);
+
+void mailimf_references_free(struct mailimf_references * references);
+
+
+
+/*
+  mailimf_subject is the parsed Subject field
+  
+  - value is the value of the field
+*/
+
+struct mailimf_subject {
+  char * sbj_value; /* != NULL */
+};
+
+struct mailimf_subject * mailimf_subject_new(char * sbj_value);
+
+void mailimf_subject_free(struct mailimf_subject * subject);
+
+
+/*
+  mailimf_comments is the parsed Comments field
+
+  - value is the value of the field
+*/
+
+struct mailimf_comments {
+  char * cm_value; /* != NULL */
+};
+
+struct mailimf_comments * mailimf_comments_new(char * cm_value);
+
+void mailimf_comments_free(struct mailimf_comments * comments);
+
+
+/*
+  mailimf_keywords is the parsed Keywords field
+
+  - list is the list of keywords
+*/
+
+struct mailimf_keywords {
+  clist * kw_list; /* list of (char *) */
+       /* != NULL */
+};
+
+struct mailimf_keywords * mailimf_keywords_new(clist * kw_list);
+
+void mailimf_keywords_free(struct mailimf_keywords * keywords);
+
+
+/*
+  mailimf_return is the parsed Return-Path field
+
+  - path is the parsed value of Return-Path
+*/
+
+struct mailimf_return {
+  struct mailimf_path * ret_path; /* != NULL */
+};
+
+struct mailimf_return *
+mailimf_return_new(struct mailimf_path * ret_path);
+
+void mailimf_return_free(struct mailimf_return * return_path);
+
+
+/*
+  mailimf_path is the parsed value of Return-Path
+
+  - addr_spec is a mailbox
+*/
+
+struct mailimf_path {
+  char * pt_addr_spec; /* can be NULL */
+};
+
+struct mailimf_path * mailimf_path_new(char * pt_addr_spec);
+
+void mailimf_path_free(struct mailimf_path * path);
+
+
+/*
+  mailimf_optional_field is a non-parsed field
+
+  - name is the name of the field
+
+  - value is the value of the field
+*/
+
+struct mailimf_optional_field {
+  char * fld_name;  /* != NULL */
+  char * fld_value; /* != NULL */
+};
+
+struct mailimf_optional_field *
+mailimf_optional_field_new(char * fld_name, char * fld_value);
+
+void mailimf_optional_field_free(struct mailimf_optional_field * opt_field);
+
+
+/*
+  mailimf_fields is the native structure that IMF module will use,
+  this module will provide an easier structure to use when parsing fields.
+
+  mailimf_single_fields is an easier structure to get parsed fields,
+  rather than iteration over the list of fields
+
+  - orig_date is the parsed "Date" field
+
+  - from is the parsed "From" field
+  
+  - sender is the parsed "Sender "field
+
+  - reply_to is the parsed "Reply-To" field
+  
+  - to is the parsed "To" field
+
+  - cc is the parsed "Cc" field
+
+  - bcc is the parsed "Bcc" field
+
+  - message_id is the parsed "Message-ID" field
+
+  - in_reply_to is the parsed "In-Reply-To" field
+
+  - references is the parsed "References" field
+
+  - subject is the parsed "Subject" field
+  
+  - comments is the parsed "Comments" field
+
+  - keywords is the parsed "Keywords" field
+*/
+
+struct mailimf_single_fields {
+  struct mailimf_orig_date * fld_orig_date;      /* can be NULL */
+  struct mailimf_from * fld_from;                /* can be NULL */
+  struct mailimf_sender * fld_sender;            /* can be NULL */
+  struct mailimf_reply_to * fld_reply_to;        /* can be NULL */
+  struct mailimf_to * fld_to;                    /* can be NULL */
+  struct mailimf_cc * fld_cc;                    /* can be NULL */
+  struct mailimf_bcc * fld_bcc;                  /* can be NULL */
+  struct mailimf_message_id * fld_message_id;    /* can be NULL */
+  struct mailimf_in_reply_to * fld_in_reply_to;  /* can be NULL */
+  struct mailimf_references * fld_references;    /* can be NULL */
+  struct mailimf_subject * fld_subject;          /* can be NULL */
+  struct mailimf_comments * fld_comments;        /* can be NULL */
+  struct mailimf_keywords * fld_keywords;        /* can be NULL */
+};
+
+
+
+
+
+
+/* internal use */
+
+void mailimf_atom_free(char * atom);
+
+void mailimf_dot_atom_free(char * dot_atom);
+
+void mailimf_dot_atom_text_free(char * dot_atom);
+
+void mailimf_quoted_string_free(char * quoted_string);
+
+void mailimf_word_free(char * word);
+
+void mailimf_phrase_free(char * phrase);
+
+void mailimf_unstructured_free(char * unstructured);
+
+void mailimf_angle_addr_free(char * angle_addr);
+
+void mailimf_display_name_free(char * display_name);
+
+void mailimf_addr_spec_free(char * addr_spec);
+
+void mailimf_local_part_free(char * local_part);
+
+void mailimf_domain_free(char * domain);
+
+void mailimf_domain_literal_free(char * domain);
+
+void mailimf_msg_id_free(char * msg_id);
+
+void mailimf_id_left_free(char * id_left);
+
+void mailimf_id_right_free(char * id_right);
+
+void mailimf_no_fold_quote_free(char * nfq);
+
+void mailimf_no_fold_literal_free(char * nfl);
+
+void mailimf_field_name_free(char * field_name);
+
+
+
+/* these are the possible returned error codes */
+
+enum {
+  MAILIMF_NO_ERROR = 0,
+  MAILIMF_ERROR_PARSE,
+  MAILIMF_ERROR_MEMORY,
+  MAILIMF_ERROR_INVAL,
+  MAILIMF_ERROR_FILE,
+};
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/plugins/mailmbox/mailimf_types_helper.c b/src/plugins/mailmbox/mailimf_types_helper.c
new file mode 100644 (file)
index 0000000..9a7146f
--- /dev/null
@@ -0,0 +1,1636 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "mailimf_types_helper.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "mailimf.h"
+
+struct mailimf_mailbox_list *
+mailimf_mailbox_list_new_empty()
+{
+  clist * list;
+  struct mailimf_mailbox_list * mb_list;
+
+  list = clist_new();
+  if (list == NULL)
+    return NULL;
+
+  mb_list = mailimf_mailbox_list_new(list);
+  if (mb_list == NULL)
+    return NULL;
+
+  return mb_list;
+}
+
+int mailimf_mailbox_list_add(struct mailimf_mailbox_list * mailbox_list,
+                            struct mailimf_mailbox * mb)
+{
+  int r;
+
+  r = clist_append(mailbox_list->mb_list, mb);
+  if (r < 0)
+    return MAILIMF_ERROR_MEMORY;
+
+  return MAILIMF_NO_ERROR;
+}
+
+int mailimf_mailbox_list_add_parse(struct mailimf_mailbox_list * mailbox_list,
+                                  char * mb_str)
+{
+  int r;
+  size_t cur_token;
+  struct mailimf_mailbox * mb;
+  int res;
+
+  cur_token = 0;
+  r = mailimf_mailbox_parse(mb_str, strlen(mb_str), &cur_token, &mb);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+  
+  r = mailimf_mailbox_list_add(mailbox_list, mb);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free;
+  }
+
+  return MAILIMF_NO_ERROR;
+
+ free:
+  mailimf_mailbox_free(mb);
+ err:
+  return res;
+}
+
+int mailimf_mailbox_list_add_mb(struct mailimf_mailbox_list * mailbox_list,
+                               char * display_name, char * address)
+{
+  int r;
+  struct mailimf_mailbox * mb;
+  int res;
+
+  mb = mailimf_mailbox_new(display_name, address);
+  if (mb == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+  
+  r = mailimf_mailbox_list_add(mailbox_list, mb);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free;
+  }
+
+  return MAILIMF_NO_ERROR;
+
+ free:
+  mailimf_mailbox_free(mb);
+ err:
+  return res;
+}
+
+
+
+struct mailimf_address_list *
+mailimf_address_list_new_empty()
+{
+  clist * list;
+  struct mailimf_address_list * addr_list;
+
+  list = clist_new();
+  if (list == NULL)
+    return NULL;
+
+  addr_list = mailimf_address_list_new(list);
+  if (addr_list == NULL)
+    return NULL;
+
+  return addr_list;
+}
+
+int mailimf_address_list_add(struct mailimf_address_list * address_list,
+                            struct mailimf_address * addr)
+{
+  int r;
+
+  r = clist_append(address_list->ad_list, addr);
+  if (r < 0)
+    return MAILIMF_ERROR_MEMORY;
+
+  return MAILIMF_NO_ERROR;
+}
+
+int mailimf_address_list_add_parse(struct mailimf_address_list * address_list,
+                                  char * addr_str)
+{
+  int r;
+  size_t cur_token;
+  struct mailimf_address * addr;
+  int res;
+
+  cur_token = 0;
+  r = mailimf_address_parse(addr_str, strlen(addr_str), &cur_token, &addr);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+  
+  r = mailimf_address_list_add(address_list, addr);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free;
+  }
+
+  return MAILIMF_NO_ERROR;
+
+ free:
+  mailimf_address_free(addr);
+ err:
+  return res;
+}
+
+int mailimf_address_list_add_mb(struct mailimf_address_list * address_list,
+                               char * display_name, char * address)
+{
+  int r;
+  struct mailimf_mailbox * mb;
+  struct mailimf_address * addr;
+  int res;
+
+  mb = mailimf_mailbox_new(display_name, address);
+  if (mb == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto err;
+  }
+
+  addr = mailimf_address_new(MAILIMF_ADDRESS_MAILBOX, mb, NULL);
+  if (addr == NULL) {
+    res = MAILIMF_ERROR_MEMORY;
+    goto free_mb;
+  }
+
+  r = mailimf_address_list_add(address_list, addr);
+  if (r != MAILIMF_NO_ERROR) {
+    res = r;
+    goto free_addr;
+  }
+
+  return MAILIMF_NO_ERROR;
+
+ free_addr:
+  mailimf_address_free(addr);
+ free_mb:
+  mailimf_mailbox_free(mb);
+ err:
+  return res;
+}
+
+
+#if 0
+struct mailimf_resent_fields_list *
+mailimf_resent_fields_list_new_empty()
+{
+  clist * list;
+  struct mailimf_resent_fields_list * rf_list;
+
+  list = clist_new();
+  if (list == NULL)
+    return NULL;
+
+  rf_list = mailimf_resent_fields_list_new(list);
+  if (rf_list == NULL)
+    return NULL;
+
+  return rf_list;
+}
+
+int mailimf_resent_fields_add(struct mailimf_resent_fields_list * fields,
+                             struct mailimf_resent_field * field)
+{
+  int r;
+
+  r = clist_append(fields->list, field);
+  if (r < 0)
+    return MAILIMF_ERROR_MEMORY;
+  
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+
+static void detach_free_common_fields(struct mailimf_orig_date * imf_date,
+                                     struct mailimf_from * imf_from,
+                                     struct mailimf_sender * imf_sender,
+                                     struct mailimf_to * imf_to,
+                                     struct mailimf_cc * imf_cc,
+                                     struct mailimf_bcc * imf_bcc,
+                                     struct mailimf_message_id * imf_msg_id)
+{
+  if (imf_date != NULL) {
+    imf_date->dt_date_time = NULL;
+    mailimf_orig_date_free(imf_date);
+  }
+  if (imf_from != NULL) {
+    imf_from->frm_mb_list = NULL;
+    mailimf_from_free(imf_from);
+  }
+  if (imf_sender != NULL) {
+    imf_sender->snd_mb = NULL;
+    mailimf_sender_free(imf_sender);
+  }
+  if (imf_to != NULL) {
+    imf_to->to_addr_list = NULL;
+    mailimf_to_free(imf_to);
+  }
+  if (imf_cc != NULL) {
+    imf_cc->cc_addr_list = NULL;
+    mailimf_to_free(imf_to);
+  }
+  if (imf_bcc != NULL) {
+    imf_bcc->bcc_addr_list = NULL;
+    mailimf_bcc_free(imf_bcc);
+  }
+  if (imf_msg_id != NULL) {
+    imf_msg_id->mid_value = NULL;
+    mailimf_message_id_free(imf_msg_id);
+  }
+}
+
+static void detach_resent_field(struct mailimf_field * field)
+{
+  field->fld_type = MAILIMF_FIELD_NONE;
+  mailimf_field_free(field);
+}
+
+int
+mailimf_resent_fields_add_data(struct mailimf_fields * fields,
+    struct mailimf_date_time * resent_date,
+    struct mailimf_mailbox_list * resent_from,
+    struct mailimf_mailbox * resent_sender,
+    struct mailimf_address_list * resent_to,
+    struct mailimf_address_list * resent_cc,
+    struct mailimf_address_list * resent_bcc,
+    char * resent_msg_id)
+{
+  struct mailimf_orig_date * imf_resent_date;
+  struct mailimf_from * imf_resent_from;
+  struct mailimf_sender * imf_resent_sender;
+  struct mailimf_to * imf_resent_to;
+  struct mailimf_cc * imf_resent_cc;
+  struct mailimf_bcc * imf_resent_bcc;
+  struct mailimf_message_id * imf_resent_msg_id;
+  struct mailimf_field * field;
+  int r;
+
+  imf_resent_date = NULL;
+  imf_resent_from = NULL;
+  imf_resent_sender = NULL;
+  imf_resent_to = NULL;
+  imf_resent_cc = NULL;
+  imf_resent_bcc = NULL;
+  imf_resent_msg_id = NULL;
+  field = NULL;
+
+  if (resent_date != NULL) {
+    imf_resent_date = mailimf_orig_date_new(resent_date);
+    if (imf_resent_date == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_RESENT_DATE,
+        NULL /* return-path */,
+        imf_resent_date /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (resent_from != NULL) {
+    imf_resent_from = mailimf_from_new(resent_from);
+    if (imf_resent_from == NULL)
+      goto free_field;
+    field = mailimf_field_new(MAILIMF_FIELD_RESENT_FROM,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        imf_resent_from /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (resent_sender != NULL) {
+    imf_resent_sender = mailimf_sender_new(resent_sender);
+    if (imf_resent_sender == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_RESENT_SENDER,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        imf_resent_sender /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (resent_to != NULL) {
+    imf_resent_to = mailimf_to_new(resent_to);
+    if (imf_resent_to == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_RESENT_TO,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        imf_resent_to /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (resent_cc != NULL) {
+    imf_resent_cc = mailimf_cc_new(resent_cc);
+    if (imf_resent_cc == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_RESENT_CC,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        imf_resent_cc /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (resent_bcc != NULL) {
+    imf_resent_bcc = mailimf_bcc_new(resent_bcc);
+    if (imf_resent_bcc == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_RESENT_BCC,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        imf_resent_bcc /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (resent_msg_id != NULL) {
+    imf_resent_msg_id = mailimf_message_id_new(resent_msg_id);
+    if (imf_resent_msg_id == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_RESENT_MSG_ID,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        imf_resent_msg_id /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  return MAILIMF_NO_ERROR;
+
+ free_field:
+  if (field != NULL) {
+    detach_resent_field(field);
+    mailimf_field_free(field);
+  }
+ free:
+  detach_free_common_fields(imf_resent_date,
+                           imf_resent_from,
+                           imf_resent_sender,
+                           imf_resent_to,
+                           imf_resent_cc,
+                           imf_resent_bcc,
+                           imf_resent_msg_id);
+  return MAILIMF_ERROR_MEMORY;
+}
+
+struct mailimf_fields *
+mailimf_resent_fields_new_with_data_all(struct mailimf_date_time *
+    resent_date,
+    struct mailimf_mailbox_list *
+    resent_from,
+    struct mailimf_mailbox *
+    resent_sender,
+    struct mailimf_address_list *
+    resent_to,
+    struct mailimf_address_list *
+    resent_cc,
+    struct mailimf_address_list *
+    resent_bcc,
+    char * resent_msg_id)
+{
+  struct mailimf_fields * resent_fields;
+  int r;
+
+  resent_fields = mailimf_fields_new_empty();
+  if (resent_fields == NULL)
+    goto err;
+
+  r = mailimf_resent_fields_add_data(resent_fields,
+      resent_date, resent_from,
+      resent_sender, resent_to,
+      resent_cc, resent_bcc,
+      resent_msg_id);
+  if (r != MAILIMF_NO_ERROR)
+    goto free;
+
+  return resent_fields;
+
+ free:
+  mailimf_fields_free(resent_fields);
+ err:
+  return NULL;
+}
+
+
+struct mailimf_fields *
+mailimf_resent_fields_new_with_data(struct mailimf_mailbox_list * from,
+    struct mailimf_mailbox * sender,
+    struct mailimf_address_list * to,
+    struct mailimf_address_list * cc,
+    struct mailimf_address_list * bcc)
+{
+  struct mailimf_date_time * date;
+  char * msg_id;
+  struct mailimf_fields * fields;
+
+  date = mailimf_get_current_date();
+  if (date == NULL)
+    goto err;
+
+  msg_id = mailimf_get_message_id();
+  if (msg_id == NULL)
+    goto free_date;
+
+  fields = mailimf_resent_fields_new_with_data_all(date,
+      from, sender, to, cc, bcc, msg_id);
+  if (fields == NULL)
+    goto free_msg_id;
+
+  return fields;
+
+ free_msg_id:
+  free(msg_id);
+ free_date:
+  mailimf_date_time_free(date);
+ err:
+  return NULL;
+}
+
+
+struct mailimf_fields *
+mailimf_fields_new_empty(void)
+{
+  clist * list;
+  struct mailimf_fields * fields_list;
+
+  list = clist_new();
+  if (list == NULL)
+    return NULL;
+
+  fields_list = mailimf_fields_new(list);
+  if (fields_list == NULL)
+    return NULL;
+
+  return fields_list;
+}
+
+int mailimf_fields_add(struct mailimf_fields * fields,
+                      struct mailimf_field * field)
+{
+  int r;
+
+  r = clist_append(fields->fld_list, field);
+  if (r < 0)
+    return MAILIMF_ERROR_MEMORY;
+  
+  return MAILIMF_NO_ERROR;
+}
+
+static void detach_free_fields(struct mailimf_orig_date * date,
+                              struct mailimf_from * from,
+                              struct mailimf_sender * sender,
+                              struct mailimf_reply_to * reply_to,
+                              struct mailimf_to * to,
+                              struct mailimf_cc * cc,
+                              struct mailimf_bcc * bcc,
+                              struct mailimf_message_id * msg_id,
+                              struct mailimf_in_reply_to * in_reply_to,
+                              struct mailimf_references * references,
+                              struct mailimf_subject * subject)
+{
+  detach_free_common_fields(date,
+      from,
+      sender,
+      to,
+      cc,
+      bcc,
+      msg_id);
+
+  if (reply_to != NULL) {
+    reply_to->rt_addr_list = NULL;
+    mailimf_reply_to_free(reply_to);
+  }
+
+  if (in_reply_to != NULL) {
+    in_reply_to->mid_list = NULL;
+    mailimf_in_reply_to_free(in_reply_to);
+  }
+
+  if (references != NULL) {
+    references->mid_list = NULL;
+    mailimf_references_free(references);
+  }
+
+  if (subject != NULL) {
+    subject->sbj_value = NULL;
+    mailimf_subject_free(subject);
+  }
+}
+
+
+static void detach_field(struct mailimf_field * field)
+{
+  field->fld_type = MAILIMF_FIELD_NONE;
+  mailimf_field_free(field);
+}
+
+int mailimf_fields_add_data(struct mailimf_fields * fields,
+                           struct mailimf_date_time * date,
+                           struct mailimf_mailbox_list * from,
+                           struct mailimf_mailbox * sender,
+                           struct mailimf_address_list * reply_to,
+                           struct mailimf_address_list * to,
+                           struct mailimf_address_list * cc,
+                           struct mailimf_address_list * bcc,
+                           char * msg_id,
+                           clist * in_reply_to,
+                           clist * references,
+                           char * subject)
+{
+  struct mailimf_orig_date * imf_date;
+  struct mailimf_from * imf_from;
+  struct mailimf_sender * imf_sender;
+  struct mailimf_reply_to * imf_reply_to;
+  struct mailimf_to * imf_to;
+  struct mailimf_cc * imf_cc;
+  struct mailimf_bcc * imf_bcc;
+  struct mailimf_message_id * imf_msg_id;
+  struct mailimf_references * imf_references;
+  struct mailimf_in_reply_to * imf_in_reply_to;
+  struct mailimf_subject * imf_subject;
+  struct mailimf_field * field;
+  int r;
+
+  imf_date = NULL;
+  imf_from = NULL;
+  imf_sender = NULL;
+  imf_reply_to = NULL;
+  imf_to = NULL;
+  imf_cc = NULL;
+  imf_bcc = NULL;
+  imf_msg_id = NULL;
+  imf_references = NULL;
+  imf_in_reply_to = NULL;
+  imf_subject =NULL;
+  field = NULL;
+
+  if (date != NULL) {
+    imf_date = mailimf_orig_date_new(date);
+    if (imf_date == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_ORIG_DATE,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        imf_date /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (from != NULL) {
+    imf_from = mailimf_from_new(from);
+    if (imf_from == NULL)
+      goto free_field;
+    field = mailimf_field_new(MAILIMF_FIELD_FROM,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        imf_from /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (sender != NULL) {
+    imf_sender = mailimf_sender_new(sender);
+    if (imf_sender == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_SENDER,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        imf_sender /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (reply_to != NULL) {
+    imf_reply_to = mailimf_reply_to_new(reply_to);
+    if (imf_reply_to == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_REPLY_TO,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        imf_reply_to /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (to != NULL) {
+    imf_to = mailimf_to_new(to);
+    if (imf_to == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_TO,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        imf_to /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (cc != NULL) {
+    imf_cc = mailimf_cc_new(cc);
+    if (imf_cc == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_CC,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        imf_cc /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (bcc != NULL) {
+    imf_bcc = mailimf_bcc_new(bcc);
+    if (imf_bcc == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_BCC,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        imf_bcc /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (msg_id != NULL) {
+    imf_msg_id = mailimf_message_id_new(msg_id);
+    if (imf_msg_id == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_MESSAGE_ID,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        imf_msg_id /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (in_reply_to != NULL) {
+    imf_in_reply_to = mailimf_in_reply_to_new(in_reply_to);
+    if (imf_in_reply_to == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_IN_REPLY_TO,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        imf_in_reply_to /* in reply to */,
+        NULL /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (references != NULL) {
+    imf_references = mailimf_references_new(references);
+    if (imf_references == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_REFERENCES,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        imf_references /* references */,
+        NULL /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  if (subject != NULL) {
+    imf_subject = mailimf_subject_new(subject);
+    if (imf_subject == NULL)
+      goto free;
+    field = mailimf_field_new(MAILIMF_FIELD_SUBJECT,
+        NULL /* return-path */,
+        NULL /* resent date */,
+        NULL /* resent from */,
+        NULL /* resent sender */,
+        NULL /* resent to */,
+        NULL /* resent cc */,
+        NULL /* resent bcc */,
+        NULL /* resent msg id */,
+        NULL /* date */,
+        NULL /* from */,
+        NULL /* sender */,
+        NULL /* reply-to */,
+        NULL /* to */,
+        NULL /* cc */,
+        NULL /* bcc */,
+        NULL /* message id */,
+        NULL /* in reply to */,
+        NULL /* references */,
+        imf_subject /* subject */,
+        NULL /* comments */,
+        NULL /* keywords */,
+        NULL /* optional field */);
+    if (field == NULL)
+      goto free;
+    r =  mailimf_fields_add(fields, field);
+    if (r != MAILIMF_NO_ERROR)
+      goto free_field;
+  }
+
+  return MAILIMF_NO_ERROR;
+
+ free_field:
+  if (field != NULL) {
+    detach_field(field);
+    mailimf_field_free(field);
+  }
+ free:
+  detach_free_fields(imf_date,
+                    imf_from,
+                    imf_sender,
+                    imf_reply_to,
+                    imf_to,
+                    imf_cc,
+                    imf_bcc,
+                    imf_msg_id,
+                    imf_in_reply_to,
+                    imf_references,
+                    imf_subject);
+
+  return MAILIMF_ERROR_MEMORY;
+}
+
+struct mailimf_fields *
+mailimf_fields_new_with_data_all(struct mailimf_date_time * date,
+                                struct mailimf_mailbox_list * from,
+                                struct mailimf_mailbox * sender,
+                                struct mailimf_address_list * reply_to,
+                                struct mailimf_address_list * to,
+                                struct mailimf_address_list * cc,
+                                struct mailimf_address_list * bcc,
+                                char * message_id,
+                                clist * in_reply_to,
+                                clist * references,
+                                char * subject)
+{
+  struct mailimf_fields * fields;
+  int r;
+
+  fields = mailimf_fields_new_empty();
+  if (fields == NULL)
+    goto err;
+
+  r = mailimf_fields_add_data(fields,
+                             date,
+                             from,
+                             sender,
+                             reply_to,
+                             to,
+                             cc,
+                             bcc,
+                             message_id,
+                             in_reply_to,
+                             references,
+                             subject);
+  if (r != MAILIMF_NO_ERROR)
+    goto free;
+
+  return fields;
+
+ free:
+  mailimf_fields_free(fields);
+ err:
+  return NULL;
+}
+
+struct mailimf_fields *
+mailimf_fields_new_with_data(struct mailimf_mailbox_list * from,
+                            struct mailimf_mailbox * sender,
+                            struct mailimf_address_list * reply_to,
+                            struct mailimf_address_list * to,
+                            struct mailimf_address_list * cc,
+                            struct mailimf_address_list * bcc,
+                            clist * in_reply_to,
+                            clist * references,
+                            char * subject)
+{
+  struct mailimf_date_time * date;
+  char * msg_id;
+  struct mailimf_fields * fields;
+
+  date = mailimf_get_current_date();
+  if (date == NULL)
+    goto err;
+
+  msg_id = mailimf_get_message_id();
+  if (msg_id == NULL)
+    goto free_date;
+
+  fields = mailimf_fields_new_with_data_all(date,
+                                           from, sender, reply_to,
+                                           to, cc, bcc,
+                                           msg_id,
+                                           in_reply_to, references,
+                                           subject);
+  if (fields == NULL)
+    goto free_msg_id;
+
+  return fields;
+
+ free_msg_id:
+  free(msg_id);
+ free_date:
+  mailimf_date_time_free(date);
+ err:
+  return NULL;
+}
+
+
+
+#define MAX_MESSAGE_ID 512
+
+char * mailimf_get_message_id(void)
+{
+  char id[MAX_MESSAGE_ID];
+  time_t now;
+  char name[MAX_MESSAGE_ID];
+  long value;
+
+  now = time(NULL);
+  value = random();
+
+  gethostname(name, MAX_MESSAGE_ID);
+  snprintf(id, MAX_MESSAGE_ID, "etPan.%lx.%lx.%x@%s",
+          now, value, getpid(), name);
+
+  return strdup(id);
+}
+
+
+
+static time_t mkgmtime(struct tm * tmp);
+
+
+struct mailimf_date_time * mailimf_get_current_date(void)
+{
+  struct tm gmt;
+  struct tm lt;
+  int off;
+  time_t now;
+  struct mailimf_date_time * date_time;
+
+  now = time(NULL);
+
+  if (gmtime_r(&now, &gmt) == NULL)
+    return NULL;
+
+  if (localtime_r(&now, &lt) == NULL)
+    return NULL;
+
+  off = (mkgmtime(&lt) - mkgmtime(&gmt)) / (60 * 60) * 100;
+
+  date_time = mailimf_date_time_new(lt.tm_mday, lt.tm_mon + 1, lt.tm_year + 1900,
+                                   lt.tm_hour, lt.tm_min, lt.tm_sec,
+                                   off);
+
+  return date_time;
+}
+
+
+
+/* mkgmtime.c - make time corresponding to a GMT timeval struct
+ $Id$
+ * Copyright (c) 1998-2000 Carnegie Mellon University.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer. 
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The name "Carnegie Mellon University" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For permission or any other legal
+ *    details, please contact  
+ *      Office of Technology Transfer
+ *      Carnegie Mellon University
+ *      5000 Forbes Avenue
+ *      Pittsburgh, PA  15213-3890
+ *      (412) 268-4387, fax: (412) 268-7395
+ *      tech-transfer@andrew.cmu.edu
+ *
+ * 4. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by Computing Services
+ *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
+ *
+ * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
+ * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ *
+ */
+/*
+ * Copyright (c) 1987, 1989, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Arthur David Olson of the National Cancer Institute.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+** Adapted from code provided by Robert Elz, who writes:
+**     The "best" way to do mktime I think is based on an idea of Bob
+**     Kridle's (so its said...) from a long time ago. (mtxinu!kridle now).
+**     It does a binary search of the time_t space.  Since time_t's are
+**     just 32 bits, its a max of 32 iterations (even at 64 bits it
+**     would still be very reasonable).
+*/
+
+/*
+  adapted for libEtPan! by DINH V. Hoa
+*/
+
+#ifndef WRONG
+#define WRONG  (-1)
+#endif /* !defined WRONG */
+
+static int tmcomp(struct tm * atmp, struct tm * btmp)
+{
+  register int result;
+  
+  if ((result = (atmp->tm_year - btmp->tm_year)) == 0 &&
+      (result = (atmp->tm_mon - btmp->tm_mon)) == 0 &&
+      (result = (atmp->tm_mday - btmp->tm_mday)) == 0 &&
+      (result = (atmp->tm_hour - btmp->tm_hour)) == 0 &&
+      (result = (atmp->tm_min - btmp->tm_min)) == 0)
+    result = atmp->tm_sec - btmp->tm_sec;
+  return result;
+}
+
+static time_t mkgmtime(struct tm * tmp)
+{
+  register int                 dir;
+  register int                 bits;
+  register int                 saved_seconds;
+  time_t                               t;
+  struct tm                    yourtm, *mytm, buft;
+  
+  yourtm = *tmp;
+  saved_seconds = yourtm.tm_sec;
+  yourtm.tm_sec = 0;
+  /*
+  ** Calculate the number of magnitude bits in a time_t
+  ** (this works regardless of whether time_t is
+  ** signed or unsigned, though lint complains if unsigned).
+  */
+  for (bits = 0, t = 1; t > 0; ++bits, t <<= 1)
+    ;
+  /*
+  ** If time_t is signed, then 0 is the median value,
+  ** if time_t is unsigned, then 1 << bits is median.
+  */
+  t = (t < 0) ? 0 : ((time_t) 1 << bits);
+  for ( ; ; ) {
+    mytm = gmtime_r(&t, &buft);
+    dir = tmcomp(mytm, &yourtm);
+    if (dir != 0) {
+      if (bits-- < 0)
+       return WRONG;
+      if (bits < 0)
+       --t;
+      else if (dir > 0)
+       t -= (time_t) 1 << bits;
+      else     t += (time_t) 1 << bits;
+      continue;
+    }
+    break;
+  }
+  t += saved_seconds;
+  return t;
+}
+
+
+
+
+
+
+
+void mailimf_single_fields_init(struct mailimf_single_fields * single_fields,
+                                struct mailimf_fields * fields)
+{
+  clistiter * cur;
+
+  memset(single_fields, 0, sizeof(struct mailimf_single_fields));
+
+  cur = clist_begin(fields->fld_list);
+  while (cur != NULL) {
+    struct mailimf_field * field;
+
+    field = clist_content(cur);
+
+    switch (field->fld_type) {
+    case MAILIMF_FIELD_ORIG_DATE:
+      if (single_fields->fld_orig_date == NULL)
+        single_fields->fld_orig_date = field->fld_data.fld_orig_date;
+      cur = clist_next(cur);
+      break;
+    case MAILIMF_FIELD_FROM:
+      if (single_fields->fld_from == NULL) {
+        single_fields->fld_from = field->fld_data.fld_from;
+        cur = clist_next(cur);
+      }
+      else {
+        clist_concat(single_fields->fld_from->frm_mb_list->mb_list,
+                     field->fld_data.fld_from->frm_mb_list->mb_list);
+        mailimf_field_free(field);
+        cur = clist_delete(fields->fld_list, cur);
+      }
+      break;
+    case MAILIMF_FIELD_SENDER:
+      if (single_fields->fld_sender == NULL)
+        single_fields->fld_sender = field->fld_data.fld_sender;
+      cur = clist_next(cur);
+      break;
+    case MAILIMF_FIELD_REPLY_TO:
+      if (single_fields->fld_reply_to == NULL) {
+        single_fields->fld_reply_to = field->fld_data.fld_reply_to;
+        cur = clist_next(cur);
+      }
+      else {
+        clist_concat(single_fields->fld_reply_to->rt_addr_list->ad_list,
+                     field->fld_data.fld_reply_to->rt_addr_list->ad_list);
+        mailimf_field_free(field);
+        cur = clist_delete(fields->fld_list, cur);
+      }
+      break;
+    case MAILIMF_FIELD_TO:
+      if (single_fields->fld_to == NULL) {
+        single_fields->fld_to = field->fld_data.fld_to;
+        cur = clist_next(cur);
+      }
+      else {
+        clist_concat(single_fields->fld_to->to_addr_list->ad_list,
+                     field->fld_data.fld_to->to_addr_list->ad_list);
+        mailimf_field_free(field);
+        cur = clist_delete(fields->fld_list, cur);
+      }
+      break;
+    case MAILIMF_FIELD_CC:
+      if (single_fields->fld_cc == NULL) {
+        single_fields->fld_cc = field->fld_data.fld_cc;
+        cur = clist_next(cur);
+      }
+      else {
+        clist_concat(single_fields->fld_cc->cc_addr_list->ad_list, 
+                     field->fld_data.fld_cc->cc_addr_list->ad_list);
+        mailimf_field_free(field);
+        cur = clist_delete(fields->fld_list, cur);
+      }
+      break;
+    case MAILIMF_FIELD_BCC:
+      if (single_fields->fld_bcc == NULL) {
+        single_fields->fld_bcc = field->fld_data.fld_bcc;
+        cur = clist_next(cur);
+      }
+      else {
+        clist_concat(single_fields->fld_bcc->bcc_addr_list->ad_list,
+                     field->fld_data.fld_bcc->bcc_addr_list->ad_list);
+        mailimf_field_free(field);
+        cur = clist_delete(fields->fld_list, cur);
+      }
+      break;
+    case MAILIMF_FIELD_MESSAGE_ID:
+      if (single_fields->fld_message_id == NULL)
+        single_fields->fld_message_id = field->fld_data.fld_message_id;
+      cur = clist_next(cur);
+      break;
+    case MAILIMF_FIELD_IN_REPLY_TO:
+      if (single_fields->fld_in_reply_to == NULL)
+        single_fields->fld_in_reply_to = field->fld_data.fld_in_reply_to;
+      cur = clist_next(cur);
+      break;
+    case MAILIMF_FIELD_REFERENCES:
+      if (single_fields->fld_references == NULL)
+        single_fields->fld_references = field->fld_data.fld_references;
+      cur = clist_next(cur);
+      break;
+    case MAILIMF_FIELD_SUBJECT:
+      if (single_fields->fld_subject == NULL)
+        single_fields->fld_subject = field->fld_data.fld_subject;
+      cur = clist_next(cur);
+      break;
+    case MAILIMF_FIELD_COMMENTS:
+      if (single_fields->fld_comments == NULL)
+        single_fields->fld_comments = field->fld_data.fld_comments;
+      cur = clist_next(cur);
+      break;
+    case MAILIMF_FIELD_KEYWORDS:
+      if (single_fields->fld_keywords == NULL)
+        single_fields->fld_keywords = field->fld_data.fld_keywords;
+      cur = clist_next(cur);
+      break;
+    default:
+      cur = clist_next(cur);
+      break;
+    }
+  }
+}
+
+
+struct mailimf_single_fields *
+mailimf_single_fields_new(struct mailimf_fields * fields)
+{
+  struct mailimf_single_fields * single_fields;
+
+  single_fields = malloc(sizeof(struct mailimf_single_fields));
+  if (single_fields == NULL)
+    goto err;
+
+  mailimf_single_fields_init(single_fields, fields);
+
+  return single_fields;
+
+ err:
+  return NULL;
+}
+
+void mailimf_single_fields_free(struct mailimf_single_fields *
+                                single_fields)
+{
+  free(single_fields);
+}
+
+struct mailimf_field * mailimf_field_new_custom(char * name, char * value)
+{
+  struct mailimf_optional_field * opt_field;
+  struct mailimf_field * field;
+
+  opt_field = mailimf_optional_field_new(name, value);
+  if (opt_field == NULL)
+    goto err;
+
+  field = mailimf_field_new(MAILIMF_FIELD_OPTIONAL_FIELD,
+      NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+      NULL, NULL, NULL, NULL,
+      NULL, NULL, NULL, NULL,
+      NULL, NULL, NULL, NULL,
+      NULL, NULL, opt_field);
+  if (field == NULL)
+    goto free_opt_field;
+
+  return field;
+  
+ free_opt_field:
+  mailimf_optional_field_free(opt_field);
+ err:
+  return NULL;
+}
diff --git a/src/plugins/mailmbox/mailimf_types_helper.h b/src/plugins/mailmbox/mailimf_types_helper.h
new file mode 100644 (file)
index 0000000..14a6d21
--- /dev/null
@@ -0,0 +1,370 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef MAILIMF_TYPES_HELPER
+
+#define MAILIMF_TYPES_HELPER
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "mailimf_types.h"
+
+/*
+  IMPORTANT NOTE:
+  
+  All allocation functions will take as argument allocated data
+  and will store these data in the structure they will allocate.
+  Data should be persistant during all the use of the structure
+  and will be freed by the free function of the structure
+
+  allocation functions will return NULL on failure
+*/
+
+/*
+  mailimf_mailbox_list_new_empty creates an empty list of mailboxes
+*/
+
+struct mailimf_mailbox_list *
+mailimf_mailbox_list_new_empty();
+
+/*
+  mailimf_mailbox_list_add adds a mailbox to the list of mailboxes
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+int mailimf_mailbox_list_add(struct mailimf_mailbox_list * mailbox_list,
+                            struct mailimf_mailbox * mb);
+
+/*
+  mailimf_mailbox_list_add_parse parse the given string
+  into a mailimf_mailbox structure and adds it to the list of mailboxes
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+int mailimf_mailbox_list_add_parse(struct mailimf_mailbox_list * mailbox_list,
+                                  char * mb_str);
+
+/*
+  mailimf_mailbox creates a mailimf_mailbox structure with the given
+  arguments and adds it to the list of mailboxes
+
+  - display_name is the name that will be displayed for this mailbox,
+    for example 'name' in '"name" <mailbox@domain>,
+    should be allocated with malloc()
+  
+  - address is the mailbox, for example 'mailbox@domain'
+    in '"name" <mailbox@domain>, should be allocated with malloc()
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+int mailimf_mailbox_list_add_mb(struct mailimf_mailbox_list * mailbox_list,
+                               char * display_name, char * address);
+
+/*
+  mailimf_address_list_new_empty creates an empty list of addresses
+*/
+
+struct mailimf_address_list *
+mailimf_address_list_new_empty();
+
+/*
+  mailimf_address_list_add adds a mailbox to the list of addresses
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+int mailimf_address_list_add(struct mailimf_address_list * address_list,
+                            struct mailimf_address * addr);
+
+/*
+  mailimf_address_list_add_parse parse the given string
+  into a mailimf_address structure and adds it to the list of addresses
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+int mailimf_address_list_add_parse(struct mailimf_address_list * address_list,
+                                  char * addr_str);
+
+/*
+  mailimf_address_list_add_mb creates a mailbox mailimf_address
+  with the given arguments and adds it to the list of addresses
+
+  - display_name is the name that will be displayed for this mailbox,
+    for example 'name' in '"name" <mailbox@domain>,
+    should be allocated with malloc()
+  
+  - address is the mailbox, for example 'mailbox@domain'
+    in '"name" <mailbox@domain>, should be allocated with malloc()
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+int mailimf_address_list_add_mb(struct mailimf_address_list * address_list,
+                               char * display_name, char * address);
+
+/*
+  mailimf_resent_fields_add_data adds a set of resent fields in the
+  given mailimf_fields structure.
+  
+  if you don't want a given field in the set to be added in the list
+  of fields, you can give NULL as argument
+
+  @param resent_msg_id sould be allocated with malloc()
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+int
+mailimf_resent_fields_add_data(struct mailimf_fields * fields,
+    struct mailimf_date_time * resent_date,
+    struct mailimf_mailbox_list * resent_from,
+    struct mailimf_mailbox * resent_sender,
+    struct mailimf_address_list * resent_to,
+    struct mailimf_address_list * resent_cc,
+    struct mailimf_address_list * resent_bcc,
+    char * resent_msg_id);
+
+/*
+  mailimf_resent_fields_new_with_data_all creates a new mailimf_fields
+  structure with a set of resent fields
+
+  if you don't want a given field in the set to be added in the list
+  of fields, you can give NULL as argument
+
+  @param resent_msg_id sould be allocated with malloc()
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+struct mailimf_fields *
+mailimf_resent_fields_new_with_data_all(struct mailimf_date_time *
+    resent_date, struct mailimf_mailbox_list * resent_from,
+    struct mailimf_mailbox * resent_sender,
+    struct mailimf_address_list * resent_to,
+    struct mailimf_address_list * resent_cc,
+    struct mailimf_address_list * resent_bcc,
+    char * resent_msg_id);
+
+/*
+  mailimf_resent_fields_new_with_data_all creates a new mailimf_fields
+  structure with a set of resent fields.
+  Resent-Date and Resent-Message-ID fields will be generated for you.
+
+  if you don't want a given field in the set to be added in the list
+  of fields, you can give NULL as argument
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+struct mailimf_fields *
+mailimf_resent_fields_new_with_data(struct mailimf_mailbox_list * from,
+    struct mailimf_mailbox * sender,
+    struct mailimf_address_list * to,
+    struct mailimf_address_list * cc,
+    struct mailimf_address_list * bcc);
+
+/*
+  this function creates a new mailimf_fields structure with no fields
+*/
+
+struct mailimf_fields *
+mailimf_fields_new_empty(void);
+
+
+/*
+  this function adds a field to the mailimf_fields structure
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+int mailimf_fields_add(struct mailimf_fields * fields,
+                      struct mailimf_field * field);
+
+
+/*
+  mailimf_fields_add_data adds a set of fields in the
+  given mailimf_fields structure.
+  
+  if you don't want a given field in the set to be added in the list
+  of fields, you can give NULL as argument
+
+  @param msg_id sould be allocated with malloc()
+  @param subject should be allocated with malloc()
+  @param in_reply_to each elements of this list should be allocated
+    with malloc()
+  @param references each elements of this list should be allocated
+    with malloc()
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+int mailimf_fields_add_data(struct mailimf_fields * fields,
+                           struct mailimf_date_time * date,
+                           struct mailimf_mailbox_list * from,
+                           struct mailimf_mailbox * sender,
+                           struct mailimf_address_list * reply_to,
+                           struct mailimf_address_list * to,
+                           struct mailimf_address_list * cc,
+                           struct mailimf_address_list * bcc,
+                           char * msg_id,
+                           clist * in_reply_to,
+                           clist * references,
+                           char * subject);
+
+/*
+  mailimf_fields_new_with_data_all creates a new mailimf_fields
+  structure with a set of fields
+
+  if you don't want a given field in the set to be added in the list
+  of fields, you can give NULL as argument
+
+  @param message_id sould be allocated with malloc()
+  @param subject should be allocated with malloc()
+  @param in_reply_to each elements of this list should be allocated
+    with malloc()
+  @param references each elements of this list should be allocated
+    with malloc()
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+struct mailimf_fields *
+mailimf_fields_new_with_data_all(struct mailimf_date_time * date,
+                                struct mailimf_mailbox_list * from,
+                                struct mailimf_mailbox * sender,
+                                struct mailimf_address_list * reply_to,
+                                struct mailimf_address_list * to,
+                                struct mailimf_address_list * cc,
+                                struct mailimf_address_list * bcc,
+                                char * message_id,
+                                clist * in_reply_to,
+                                clist * references,
+                                char * subject);
+
+/*
+  mailimf_fields_new_with_data creates a new mailimf_fields
+  structure with a set of fields
+  Date and Message-ID fields will be generated for you.
+
+  if you don't want a given field in the set to be added in the list
+  of fields, you can give NULL as argument
+
+  @param subject should be allocated with malloc()
+  @param in_reply_to each elements of this list should be allocated
+    with malloc()
+  @param references each elements of this list should be allocated
+    with malloc()
+
+  @return MAILIMF_NO_ERROR will be returned on success,
+  other code will be returned otherwise
+*/
+
+struct mailimf_fields *
+mailimf_fields_new_with_data(struct mailimf_mailbox_list * from,
+                            struct mailimf_mailbox * sender,
+                            struct mailimf_address_list * reply_to,
+                            struct mailimf_address_list * to,
+                            struct mailimf_address_list * cc,
+                            struct mailimf_address_list * bcc,
+                            clist * in_reply_to,
+                            clist * references,
+                            char * subject);
+
+/*
+  this function returns an allocated message identifier to
+  use in a Message-ID or Resent-Message-ID field
+*/
+
+char * mailimf_get_message_id(void);
+
+/*
+  this function returns a mailimf_date_time structure to
+  use in a Date or Resent-Date field
+*/
+
+struct mailimf_date_time * mailimf_get_current_date(void);
+
+
+/*
+  mailimf_single_fields_init fills a mailimf_single_fields structure
+  with the content of a mailimf_fields structure
+*/
+
+void mailimf_single_fields_init(struct mailimf_single_fields * single_fields,
+                                struct mailimf_fields * fields);
+
+/*
+  mailimf_single_fields_new creates a new mailimf_single_fields and
+  fills the structure with mailimf_fields
+*/
+
+struct mailimf_single_fields *
+mailimf_single_fields_new(struct mailimf_fields * fields);
+
+void mailimf_single_fields_free(struct mailimf_single_fields *
+                                single_fields);
+
+/*
+  mailimf_field_new_custom creates a new field of type optional
+
+  @param name should be allocated with malloc()
+  @param value should be allocated with malloc()
+*/
+
+struct mailimf_field * mailimf_field_new_custom(char * name, char * value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/plugins/mailmbox/mailimf_write.c b/src/plugins/mailmbox/mailimf_write.c
new file mode 100644 (file)
index 0000000..2935f7f
--- /dev/null
@@ -0,0 +1,2021 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#include "mailimf_write.h"
+
+#include <time.h>
+#include <string.h>
+#include <ctype.h>
+
+#define MAX_MAIL_COL 72
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#define MAX_VALID_IMF_LINE 998
+
+static int mailimf_orig_date_write(FILE * f, int * col,
+                                  struct mailimf_orig_date * date);
+static int mailimf_date_time_write(FILE * f, int * col,
+                                  struct mailimf_date_time * date_time);
+static int mailimf_from_write(FILE * f, int * col,
+                             struct mailimf_from * from);
+static int mailimf_sender_write(FILE * f, int * col,
+                               struct mailimf_sender * sender);
+static int mailimf_reply_to_write(FILE * f, int * col,
+                                 struct mailimf_reply_to * reply_to);
+static int mailimf_to_write(FILE * f, int * col,
+                           struct mailimf_to * to);
+static int mailimf_cc_write(FILE * f, int * col,
+                           struct mailimf_cc * to);
+static int mailimf_bcc_write(FILE * f, int * col,
+                            struct mailimf_bcc * to);
+static int mailimf_message_id_write(FILE * f, int * col,
+                                   struct mailimf_message_id * message_id);
+static int mailimf_msg_id_list_write(FILE * f, int * col,
+                                    clist * list);
+static int mailimf_in_reply_to_write(FILE * f, int * col,
+                                    struct mailimf_in_reply_to *
+                                    in_reply_to);
+static int mailimf_references_write(FILE * f, int * col,
+                                   struct mailimf_references * references);
+static int mailimf_subject_write(FILE * f, int * col,
+                                struct mailimf_subject * subject);
+
+static int mailimf_address_write(FILE * f, int * col,
+                                struct mailimf_address * addr);
+static int mailimf_group_write(FILE * f, int * col,
+                              struct mailimf_group * group);
+
+static int mailimf_mailbox_write(FILE * f, int * col,
+                                struct mailimf_mailbox * mb);
+
+static int mailimf_comments_write(FILE * f, int * col,
+                                 struct mailimf_comments * comments);
+
+static int mailimf_optional_field_write(FILE * f, int * col,
+                                       struct mailimf_optional_field * field);
+
+static int mailimf_keywords_write(FILE * f, int * col,
+                                 struct mailimf_keywords * keywords);
+
+static int mailimf_return_write(FILE * f, int * col,
+                               struct mailimf_return * return_path);
+
+static int mailimf_path_write(FILE * f, int * col,
+                             struct mailimf_path * path);
+
+static int mailimf_resent_date_write(FILE * f, int * col,
+                                    struct mailimf_orig_date * date);
+
+static int mailimf_resent_from_write(FILE * f, int * col,
+                                    struct mailimf_from * from);
+
+static int mailimf_resent_sender_write(FILE * f, int * col,
+                                      struct mailimf_sender * sender);
+
+static int mailimf_resent_to_write(FILE * f, int * col,
+                                  struct mailimf_to * to);
+
+static int mailimf_resent_cc_write(FILE * f, int * col,
+                                  struct mailimf_cc * cc);
+
+static int mailimf_resent_bcc_write(FILE * f, int * col,
+                                   struct mailimf_bcc * bcc);
+
+static int
+mailimf_resent_msg_id_write(FILE * f, int * col,
+                           struct mailimf_message_id * message_id);
+
+
+
+/* ************************ */
+
+#if 0
+int mailimf_string_write(FILE * f, int * col,
+                        char * str, size_t length)
+{
+  int r;
+
+  if (length != 0) {
+    r = fwrite(str, sizeof(char), length, f);
+    if (r < 0)
+      return MAILIMF_ERROR_FILE;
+    * col += length;
+  }
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+#define CRLF "\r\n"
+#define HEADER_FOLD "\r\n "
+
+static inline int flush_buf(FILE * f, const char * str, size_t length)
+{
+  if (length != 0) {
+    int r;
+    
+    r = fwrite(str, 1, length, f);
+    if (r == 0)
+      return MAILIMF_ERROR_FILE;
+  }
+  return MAILIMF_NO_ERROR;
+}
+
+#define CUT_AT_MAX_VALID_IMF_LINE
+
+int mailimf_string_write(FILE * f, int * col,
+    const char * str, size_t length)
+{
+  int r;
+  size_t count;
+  const char * block_begin;
+  const char * p;
+  int done;
+
+  p = str;
+  block_begin = str;
+  count = 0;
+  
+  while (length > 0) {
+#ifdef CUT_AT_MAX_VALID_IMF_LINE
+    if (count >= 998) {
+      /*
+        cut lines at maximum valid length for internet message
+        format standard (currently RFC 2822)
+        
+        This should not happen.
+        In case there are some lines larger than 998 in body,
+        the encoding must be changed into base64 or quoted-printable
+        so that wrapping to 72 columns is done.
+      */
+      
+      r = flush_buf(f, block_begin, count);
+      if (r != MAILIMF_NO_ERROR)
+        return r;
+      
+      r = fwrite(CRLF, 1, sizeof(CRLF) - 1, f);
+      if (r == 0)
+        return MAILIMF_ERROR_FILE;
+      
+      count = 0;
+      block_begin = p;
+      
+      * col = 0;
+    }
+#endif
+    switch (* p) {
+    case '\n':
+      r = flush_buf(f, block_begin, count);
+      if (r != MAILIMF_NO_ERROR)
+        return r;
+      
+      r = fwrite(CRLF, 1, sizeof(CRLF) - 1, f);
+      if (r == 0)
+        return MAILIMF_ERROR_FILE;
+      
+      p ++;
+      length --;
+      count = 0;
+      block_begin = p;
+      
+      * col = 0;
+      break;
+      
+    case '\r':
+      done = 0;
+      if (length >= 2) {
+        if (* (p + 1) == '\n') {
+          r = flush_buf(f, block_begin, count);
+          if (r != MAILIMF_NO_ERROR)
+            return r;
+          
+          r = fwrite(CRLF, 1, sizeof(CRLF) - 1, f);
+          if (r == 0)
+            return MAILIMF_ERROR_FILE;
+          
+          p += 2;
+          length -= 2;
+          count = 0;
+          block_begin = p;
+          
+          * col = 0;
+          
+          done = 1;
+        }
+      }
+      if (!done) {
+        r = flush_buf(f, block_begin, count);
+        if (r != MAILIMF_NO_ERROR)
+          return r;
+        
+        r = fwrite(CRLF, 1, sizeof(CRLF) - 1, f);
+        if (r == 0)
+          return MAILIMF_ERROR_FILE;
+        
+        p ++;
+        length --;
+        count = 0;
+        block_begin = p;
+        
+        * col = 0;
+      }
+      break;
+      
+    default:
+      p ++;
+      count ++;
+      length --;
+      break;
+    }
+  }
+  
+  r = flush_buf(f, block_begin, count);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+  * col += count;
+  
+  return MAILIMF_NO_ERROR;
+}
+
+#if 0
+int mailimf_header_string_write(FILE * f, int * col,
+    char * str, size_t length)
+{
+  char * p;
+  char * block_begin;
+  int current_col;
+  char * last_cut;
+  int r;
+  int first;
+  
+  if (* col + length < MAX_MAIL_COL)
+    return mailimf_string_write(f, col, str, length);
+  
+  first = 1;
+  p = str;
+  block_begin = p;
+  last_cut = block_begin;
+  current_col = * col;
+  
+  while (1) {
+    if (current_col >= MAX_MAIL_COL) {
+      /* if we reach the maximum recommanded size of line */
+      if (last_cut == block_begin) {
+        /* if we could not find any place to cut */
+        if (first) {
+          /* fold the header */
+          r = mailimf_string_write(f, col, HEADER_FOLD,
+              sizeof(HEADER_FOLD) - 1);
+          if (r != MAILIMF_NO_ERROR)
+            return r;
+          current_col = * col + p - block_begin;
+          first = 0;
+        }
+        else {
+          /* cut the header */
+          r = mailimf_string_write(f, col, block_begin, p - block_begin);
+          if (r != MAILIMF_NO_ERROR)
+            return r;
+          r = mailimf_string_write(f, col, HEADER_FOLD,
+              sizeof(HEADER_FOLD) - 1);
+          if (r != MAILIMF_NO_ERROR)
+            return r;
+          first = 0;
+          block_begin = p;
+          last_cut = block_begin;
+          current_col = * col + p - block_begin;
+        }
+      }
+      else {
+        /* if we found a place to cut */
+        r = mailimf_string_write(f, col, block_begin, last_cut - block_begin);
+        if (r != MAILIMF_NO_ERROR)
+          return r;
+        r = mailimf_string_write(f, col, HEADER_FOLD,
+            sizeof(HEADER_FOLD) - 1);
+        if (r != MAILIMF_NO_ERROR)
+          return r;
+        first = 0;
+        block_begin = last_cut;
+        last_cut = block_begin;
+        current_col = * col + p - block_begin;
+        continue;
+      }
+    }
+    else {
+      if (length == 0)
+        break;
+      
+      switch (* p) {
+      case ' ':
+      case '\t':
+        last_cut = p;
+        current_col ++;
+        break;
+        
+      case '\r':
+      case '\n':
+        current_col = 0;
+        break;
+        
+      default:
+        current_col ++;
+        break;
+      }
+      
+      p ++;
+      length --;
+    }
+  }
+  
+  return mailimf_string_write(f, col, block_begin, p - block_begin);
+}
+#endif
+
+#if 0
+enum {
+  STATE_LOWER_72,
+  STATE_LOWER_72_CUT,
+  STATE_EQUAL_72,
+  STATE_LOWER_998,
+  STATE_EQUAL_998,
+};
+
+int mailimf_header_string_write(FILE * f, int * col,
+    const char * str, size_t length)
+{
+  int state;
+  const char * p;
+  const char * block_begin;
+  size_t size;
+  const char * cut;
+  int r;
+  
+  if (* col < MAX_MAIL_COL)
+    state = STATE_LOWER_72_CUT;
+  else if (* col == MAX_MAIL_COL)
+    state = STATE_EQUAL_72;
+  else if (* col < MAX_VALID_IMF_LINE)
+    state = STATE_LOWER_998;
+  else
+    state = STATE_EQUAL_998;
+  
+  p = str;
+  block_begin = p;
+  size = * col;
+  cut = p;
+  
+  while (length > 0) {
+    switch (state) {
+    case STATE_LOWER_72:
+      switch (* p) {
+      case '\r':
+      case '\n':
+        p ++;
+        length --;
+        size = 0;
+        break;
+      
+      case ' ':
+      case '\t':
+        cut = p;
+        p ++;
+        length --;
+        size ++;
+        state = STATE_LOWER_72_CUT;
+        break;
+      
+      default:
+        if (size < MAX_MAIL_COL - 1) {
+          p ++;
+          length --;
+          size ++;
+        }
+        else {
+          state = STATE_EQUAL_72;
+          p ++;
+          length --;
+          size ++;
+        }
+        break;
+      }
+      break; /* end of STATE_LOWER_72 */
+    
+    case STATE_LOWER_72_CUT:
+      switch (* p) {
+      case '\r':
+      case '\n':
+        p ++;
+        length --;
+        size = 0;
+        state = STATE_LOWER_72;
+        break;
+      
+      case ' ':
+      case '\t':
+        cut = p;
+        p ++;
+        length --;
+        size ++;
+        break;
+      
+      default:
+        if (size < MAX_MAIL_COL) {
+          p ++;
+          length --;
+          size ++;
+        }
+        else {
+          r = mailimf_string_write(f, col, block_begin, cut - block_begin);
+          if (r != MAILIMF_NO_ERROR)
+            return r;
+          r = mailimf_string_write(f, col, HEADER_FOLD,
+              sizeof(HEADER_FOLD) - 1);
+          if (r != MAILIMF_NO_ERROR)
+            return r;
+          p ++;
+          length --;
+          block_begin = cut;
+          if ((* block_begin == ' ') || (* block_begin == '\t'))
+            block_begin ++;
+          size = p - block_begin + * col;
+          state = STATE_LOWER_72;
+        }
+        break;
+      }
+      break; /* end of STATE_LOWER_72_CUT */
+
+    case STATE_EQUAL_72:
+      switch (* p) {
+      case '\r':
+      case '\n':
+        p ++;
+        length --;
+        size = 0;
+        state = STATE_LOWER_72;
+        break;
+      
+      case ' ':
+      case '\t':
+        r = mailimf_string_write(f, col, block_begin, p - block_begin);
+        if (r != MAILIMF_NO_ERROR)
+          return r;
+        r = mailimf_string_write(f, col, HEADER_FOLD,
+            sizeof(HEADER_FOLD) - 1);
+        if (r != MAILIMF_NO_ERROR)
+          return r;
+        p ++;
+        length --;
+        block_begin = p;
+        size = p - block_begin + * col;
+        state = STATE_LOWER_72;
+        break;
+      
+      default:
+        p ++;
+        length --;
+        size ++;
+        state = STATE_LOWER_998;
+        break;
+      }
+      break; /* end of STATE_EQUAL_72 */
+
+    case STATE_LOWER_998:
+      switch (* p) {
+      case '\r':
+      case '\n':
+        p ++;
+        length --;
+        size = 0;
+        state = STATE_LOWER_72;
+        break;
+      
+      case ' ':
+      case '\t':
+        r = mailimf_string_write(f, col, block_begin, p - block_begin);
+        if (r != MAILIMF_NO_ERROR)
+          return r;
+        r = mailimf_string_write(f, col, HEADER_FOLD,
+            sizeof(HEADER_FOLD) - 1);
+        if (r != MAILIMF_NO_ERROR)
+          return r;
+        p ++;
+        length --;
+        block_begin = p;
+        size = p - block_begin + * col;
+        state = STATE_LOWER_72;
+        break;
+      
+      default:
+        if (size < MAX_VALID_IMF_LINE - 1) {
+          p ++;
+          length --;
+          size ++;
+        }
+        else {
+          p ++;
+          length --;
+          size = 0;
+          state = STATE_EQUAL_998;
+        }
+        break;
+      }
+      break; /* end of STATE_LOWER_998 */
+
+    case STATE_EQUAL_998:
+      switch (* p) {
+      case '\r':
+      case '\n':
+        p ++;
+        length --;
+        size = 0;
+        state = STATE_LOWER_72;
+        break;
+      
+      case ' ':
+      case '\t':
+        r = mailimf_string_write(f, col, block_begin, p - block_begin);
+        if (r != MAILIMF_NO_ERROR)
+          return r;
+        r = mailimf_string_write(f, col, HEADER_FOLD,
+            sizeof(HEADER_FOLD) - 1);
+        if (r != MAILIMF_NO_ERROR)
+          return r;
+        p ++;
+        length --;
+        block_begin = p;
+        size = p - block_begin + * col;
+        state = STATE_LOWER_72;
+        break;
+      
+      default:
+#ifdef CUT_AT_MAX_VALID_IMF_LINE
+        r = mailimf_string_write(f, col, block_begin, p - block_begin);
+        if (r != MAILIMF_NO_ERROR)
+          return r;
+        r = mailimf_string_write(f, col, HEADER_FOLD,
+            sizeof(HEADER_FOLD) - 1);
+        if (r != MAILIMF_NO_ERROR)
+          return r;
+        p ++;
+        length --;
+        block_begin = p;
+        size = p - block_begin + * col;
+        state = STATE_LOWER_72;
+#else
+        p ++;
+        length --;
+        size ++;
+#endif
+        break;
+      }
+      break; /* end of STATE_EQUAL_998 */
+    }
+  }
+  
+  r = mailimf_string_write(f, col, block_begin, p - block_begin);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+  
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+enum {
+  STATE_BEGIN,
+  STATE_WORD,
+  STATE_SPACE,
+};
+
+int mailimf_header_string_write(FILE * f, int * col,
+    const char * str, size_t length)
+{
+  int state;
+  const char * p;
+  const char * word_begin;
+  const char * word_end;
+  const char * next_word;
+  int first;
+  
+  state = STATE_BEGIN;
+  
+  p = str;
+  word_begin = p;
+  word_end = p;
+  next_word = p;
+  first = 1;
+  
+  while (length > 0) {
+    switch (state) {
+    case STATE_BEGIN:
+      switch (* p) {
+      case '\r':
+      case '\n':
+      case ' ':
+      case '\t':
+        p ++;
+        length --;
+        break;
+      
+      default:
+        word_begin = p;
+        state = STATE_WORD;
+        break;
+      }
+      break;
+      
+    case STATE_SPACE:
+      switch (* p) {
+      case '\r':
+      case '\n':
+      case ' ':
+      case '\t':
+        p ++;
+        length --;
+        break;
+      
+      default:
+        word_begin = p;
+        state = STATE_WORD;
+        break;
+      }
+      break;
+
+    case STATE_WORD:
+      switch (* p) {
+      case '\r':
+      case '\n':
+      case ' ':
+      case '\t':
+        if (p - word_begin + (* col) + 1 > MAX_MAIL_COL)
+          mailimf_string_write(f, col, HEADER_FOLD,
+              sizeof(HEADER_FOLD) - 1);
+        else {
+          if (!first)
+            mailimf_string_write(f, col, " ", 1);
+        }
+        first = 0;
+        mailimf_string_write(f, col, word_begin, p - word_begin);
+        state = STATE_SPACE;
+        break;
+        
+      default:
+        if (p - word_begin + (* col) >= MAX_VALID_IMF_LINE) {
+          mailimf_string_write(f, col, word_begin, p - word_begin);
+          mailimf_string_write(f, col, HEADER_FOLD,
+              sizeof(HEADER_FOLD) - 1);
+          word_begin = p;
+        }
+        p ++;
+        length --;
+        break;
+      }
+      break;
+    }
+  }
+  
+  if (state == STATE_WORD) {
+    if (p - word_begin + (* col) >= MAX_MAIL_COL)
+      mailimf_string_write(f, col, HEADER_FOLD,
+          sizeof(HEADER_FOLD) - 1);
+    else {
+      if (!first)
+        mailimf_string_write(f, col, " ", 1);
+    }
+    first = 0;
+    mailimf_string_write(f, col, word_begin, p - word_begin);
+  }
+  
+  return MAILIMF_NO_ERROR;
+}
+
+int mailimf_envelope_fields_write(FILE * f, int * col,
+                                 struct mailimf_fields * fields)
+{
+  clistiter * cur;
+
+  for(cur = clist_begin(fields->fld_list) ; cur != NULL ;
+      cur = clist_next(cur)) {
+    int r;
+    struct mailimf_field * field;
+    
+    field = clist_content(cur);
+    if (field->fld_type != MAILIMF_FIELD_OPTIONAL_FIELD) {
+      r = mailimf_field_write(f, col, field);
+      if (r != MAILIMF_NO_ERROR)
+       return r;
+    }
+  }
+
+  return MAILIMF_NO_ERROR;
+}
+
+int mailimf_fields_write(FILE * f, int * col,
+                        struct mailimf_fields * fields)
+{
+  clistiter * cur;
+
+  for(cur = clist_begin(fields->fld_list) ; cur != NULL ;
+      cur = clist_next(cur)) {
+    int r;
+    
+    r = mailimf_field_write(f, col, clist_content(cur));
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  return MAILIMF_NO_ERROR;
+}
+
+#if 0
+int mailimf_unparsed_fields_write(FILE * f, int * col,
+                                 struct mailimf_unparsed_fields * fields)
+{
+  clistiter * cur;
+
+  for(cur = clist_begin(fields->list) ; cur != NULL ; cur = cur->next) {
+    int r;
+    
+    r = mailimf_optional_field_write(f, col, cur->data);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+int mailimf_field_write(FILE * f, int * col,
+                        struct mailimf_field * field)
+{
+  int r;
+  
+  switch (field->fld_type) {
+  case MAILIMF_FIELD_RETURN_PATH:
+    r = mailimf_return_write(f, col, field->fld_data.fld_return_path);
+    break;
+  case MAILIMF_FIELD_RESENT_DATE:
+    r = mailimf_resent_date_write(f, col, field->fld_data.fld_resent_date);
+    break;
+  case MAILIMF_FIELD_RESENT_FROM:
+    r = mailimf_resent_from_write(f, col, field->fld_data.fld_resent_from);
+    break;
+  case MAILIMF_FIELD_RESENT_SENDER:
+    r = mailimf_resent_sender_write(f, col, field->fld_data.fld_resent_sender);
+    break;
+  case MAILIMF_FIELD_RESENT_TO:
+    r = mailimf_resent_to_write(f, col, field->fld_data.fld_resent_to);
+    break;
+  case MAILIMF_FIELD_RESENT_CC:
+    r = mailimf_resent_cc_write(f, col, field->fld_data.fld_resent_cc);
+    break;
+  case MAILIMF_FIELD_RESENT_BCC:
+    r = mailimf_resent_bcc_write(f, col, field->fld_data.fld_resent_bcc);
+    break;
+  case MAILIMF_FIELD_RESENT_MSG_ID:
+    r = mailimf_resent_msg_id_write(f, col, field->fld_data.fld_resent_msg_id);
+    break;
+  case MAILIMF_FIELD_ORIG_DATE:
+    r = mailimf_orig_date_write(f, col, field->fld_data.fld_orig_date);
+    break;
+  case MAILIMF_FIELD_FROM:
+    r = mailimf_from_write(f, col, field->fld_data.fld_from);
+    break;
+  case MAILIMF_FIELD_SENDER:
+    r = mailimf_sender_write(f, col, field->fld_data.fld_sender);
+    break;
+  case MAILIMF_FIELD_REPLY_TO:
+    r = mailimf_reply_to_write(f, col, field->fld_data.fld_reply_to);
+    break;
+  case MAILIMF_FIELD_TO:
+    r = mailimf_to_write(f, col, field->fld_data.fld_to);
+    break;
+  case MAILIMF_FIELD_CC:
+    r = mailimf_cc_write(f, col, field->fld_data.fld_cc);
+    break;
+  case MAILIMF_FIELD_BCC:
+    r = mailimf_bcc_write(f, col, field->fld_data.fld_bcc);
+    break;
+  case MAILIMF_FIELD_MESSAGE_ID:
+    r = mailimf_message_id_write(f, col, field->fld_data.fld_message_id);
+    break;
+  case MAILIMF_FIELD_IN_REPLY_TO:
+    r = mailimf_in_reply_to_write(f, col, field->fld_data.fld_in_reply_to);
+    break;
+  case MAILIMF_FIELD_REFERENCES:
+    r = mailimf_references_write(f, col, field->fld_data.fld_references);
+    break;
+  case MAILIMF_FIELD_SUBJECT:
+    r = mailimf_subject_write(f, col, field->fld_data.fld_subject);
+    break;
+  case MAILIMF_FIELD_COMMENTS:
+    r = mailimf_comments_write(f, col, field->fld_data.fld_comments);
+    break;
+  case MAILIMF_FIELD_KEYWORDS:
+    r = mailimf_keywords_write(f, col, field->fld_data.fld_keywords);
+    break;
+  case MAILIMF_FIELD_OPTIONAL_FIELD:
+    r = mailimf_optional_field_write(f, col, field->fld_data.fld_optional_field);
+    break;
+  default:
+    r = MAILIMF_ERROR_INVAL;
+    break;
+  }
+
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_orig_date_write(FILE * f, int * col,
+                                  struct mailimf_orig_date * date)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Date: ", 6);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_date_time_write(f, col, date->dt_date_time);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+#define MAX_DATE_STR 256
+
+/* 0 = Sunday */
+/* y > 1752 */
+
+static int dayofweek(int year, int month, int day)
+{
+  static int offset[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
+
+  year -= month < 3;
+
+  return (year + year/4 - year/100 + year/400 + offset[month-1] + day) % 7;
+}
+
+static const char * week_of_day_str[] = { "Sun", "Mon", "Tue", "Wed", "Thu",
+                                          "Fri", "Sat"};
+static const char * month_str[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+                                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+
+static int mailimf_date_time_write(FILE * f, int * col,
+                                  struct mailimf_date_time * date_time)
+{
+  int r;
+  char date_str[MAX_DATE_STR];
+#if 0
+  struct tm tmval;
+  time_t timeval;
+#endif
+  int wday;
+  
+#if 0
+  tmval.tm_sec  = date_time->sec;
+  tmval.tm_min  = date_time->min;
+  tmval.tm_hour  = date_time->hour;
+  tmval.tm_sec  = date_time->sec;
+  tmval.tm_mday = date_time->day;
+  tmval.tm_mon = date_time->month - 1;
+  tmval.tm_year = date_time->year - 1900;
+  tmval.tm_isdst = 1;
+
+  timeval = mktime(&tmval);
+  
+  localtime_r(&timeval, &tmval);
+#endif
+
+  wday = dayofweek(date_time->dt_year, date_time->dt_month, date_time->dt_day);
+
+  snprintf(date_str, MAX_DATE_STR, "%s, %i %s %i %02i:%02i:%02i %+05i",
+      week_of_day_str[wday], date_time->dt_day,
+      month_str[date_time->dt_month - 1],
+      date_time->dt_year, date_time->dt_hour,
+      date_time->dt_min, date_time->dt_sec,
+      date_time->dt_zone);
+
+  r = mailimf_string_write(f, col, date_str, strlen(date_str));
+
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_from_write(FILE * f, int * col,
+                             struct mailimf_from * from)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "From: ", 6);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_mailbox_list_write(f, col, from->frm_mb_list);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_sender_write(FILE * f, int * col,
+                               struct mailimf_sender * sender)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Sender: ", 8);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_mailbox_write(f, col, sender->snd_mb);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_reply_to_write(FILE * f, int * col,
+                                 struct mailimf_reply_to * reply_to)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Reply-To: ", 10);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_address_list_write(f, col, reply_to->rt_addr_list);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_to_write(FILE * f, int * col,
+                           struct mailimf_to * to)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "To: ", 4);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_address_list_write(f, col, to->to_addr_list);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_cc_write(FILE * f, int * col,
+                           struct mailimf_cc * cc)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Cc: ", 4);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_address_list_write(f, col, cc->cc_addr_list);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_bcc_write(FILE * f, int * col,
+                            struct mailimf_bcc * bcc)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Bcc: ", 5);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  if (bcc->bcc_addr_list != NULL) {
+    r =  mailimf_address_list_write(f, col, bcc->bcc_addr_list);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_message_id_write(FILE * f, int * col,
+                                   struct mailimf_message_id * message_id)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Message-ID: ", 12);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "<", 1);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col,
+      message_id->mid_value,
+      strlen(message_id->mid_value));
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, ">", 1);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_msg_id_list_write(FILE * f, int * col, clist * mid_list)
+{
+  clistiter * cur;
+  int r;
+  int first;
+
+  first = TRUE;
+
+  for(cur = clist_begin(mid_list) ; cur != NULL ; cur = clist_next(cur)) {
+    char * msgid;
+    size_t len;
+
+    msgid = clist_content(cur);
+    len = strlen(msgid);
+    
+    /*
+      XXX - if this is the first message ID, don't fold.
+      This is a workaround for a bug of old versions of INN.
+    */
+    if (!first) {
+      if (* col > 1) {
+        
+        if (* col + len >= MAX_MAIL_COL) {
+          r = mailimf_string_write(f, col, "\r\n ", 3);
+          if (r != MAILIMF_NO_ERROR)
+            return r;
+#if 0
+          * col = 1;
+#endif
+          first = TRUE;
+        }
+      }
+    }
+    
+    if (!first) {
+      r = mailimf_string_write(f, col, " ", 1);
+      if (r != MAILIMF_NO_ERROR)
+       return r;
+    }
+    else {
+      first = FALSE;
+    }
+
+    r = mailimf_string_write(f, col, "<", 1);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+
+    r = mailimf_string_write(f, col, msgid, len);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+
+    r = mailimf_string_write(f, col, ">", 1);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_in_reply_to_write(FILE * f, int * col,
+                                    struct mailimf_in_reply_to * in_reply_to)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "In-Reply-To: ", 13);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_msg_id_list_write(f, col, in_reply_to->mid_list);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_references_write(FILE * f, int * col,
+                                   struct mailimf_references * references)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "References: ", 12);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_msg_id_list_write(f, col, references->mid_list);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+
+static int mailimf_subject_write(FILE * f, int * col,
+                                struct mailimf_subject * subject)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Subject: ", 9);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_header_string_write(f, col,
+      subject->sbj_value, strlen(subject->sbj_value));
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+int mailimf_address_list_write(FILE * f, int * col,
+    struct mailimf_address_list * addr_list)
+{
+  clistiter * cur;
+  int r;
+  int first;
+
+  first = TRUE;
+
+  for(cur = clist_begin(addr_list->ad_list) ; cur != NULL ;
+      cur = clist_next(cur)) {
+    struct mailimf_address * addr;
+
+    addr = clist_content(cur);
+
+    if (!first) {
+      r = mailimf_string_write(f, col, ", ", 2);
+      if (r != MAILIMF_NO_ERROR)
+       return r;
+    }
+    else {
+      first = FALSE;
+    }
+
+    r = mailimf_address_write(f, col, addr);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_address_write(FILE * f, int * col,
+                                struct mailimf_address * addr)
+{
+  int r;
+
+  switch(addr->ad_type) {
+  case MAILIMF_ADDRESS_MAILBOX:
+    r = mailimf_mailbox_write(f, col, addr->ad_data.ad_mailbox);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+
+    break;
+
+  case MAILIMF_ADDRESS_GROUP:
+    r = mailimf_group_write(f, col, addr->ad_data.ad_group);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+    
+    break;
+  }
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_group_write(FILE * f, int * col,
+                              struct mailimf_group * group)
+{
+  int r;
+
+  r = mailimf_header_string_write(f, col, group->grp_display_name,
+      strlen(group->grp_display_name));
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, ": ", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+  
+  if (group->grp_mb_list != NULL) {
+    r = mailimf_mailbox_list_write(f, col, group->grp_mb_list);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  r = mailimf_string_write(f, col, ";", 1);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+int mailimf_mailbox_list_write(FILE * f, int * col,
+    struct mailimf_mailbox_list * mb_list)
+{
+  clistiter * cur;
+  int r;
+  int first;
+
+  first = TRUE;
+
+  for(cur = clist_begin(mb_list->mb_list) ; cur != NULL ;
+      cur = clist_next(cur)) {
+    struct mailimf_mailbox * mb;
+
+    mb = clist_content(cur);
+
+    if (!first) {
+      r = mailimf_string_write(f, col, ", ", 2);
+      if (r != MAILIMF_NO_ERROR)
+       return r;
+    }
+    else {
+      first = FALSE;
+    }
+
+    r = mailimf_mailbox_write(f, col, mb);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+int mailimf_quoted_string_write(FILE * f, int * col,
+    const char * string, size_t len)
+{
+  int r;
+  size_t i;
+
+  fputc('\"', f);
+  for(i = 0 ; i < len ; i ++) {
+    switch (string[i]) {
+    case '\\':
+    case '\"':
+      r = fputc('\\', f);
+      if (r < 0)
+       return MAILIMF_ERROR_FILE;
+      r = fputc(string[i], f);
+      if (r < 0)
+       return MAILIMF_ERROR_FILE;
+      (* col) += 2;
+      break;
+
+    default:
+      r = fputc(string[i], f);
+      if (r < 0)
+       return MAILIMF_ERROR_FILE;
+      (* col) ++;
+      break;
+    }
+  }
+  fputc('\"', f);
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+/*
+static int 
+atext           =       ALPHA / DIGIT / ; Any character except controls,
+                        "!" / "#" /     ;  SP, and specials.
+                        "$" / "%" /     ;  Used for atoms
+                        "&" / "'" /
+                        "*" / "+" /
+                        "-" / "/" /
+                        "=" / "?" /
+                        "^" / "_" /
+                        "`" / "{" /
+                        "|" / "}" /
+                        "~"
+*/
+
+static int is_atext(const char * s)
+{
+  const char * p;
+
+  for(p = s ; * p != 0 ; p ++) {
+    if (isalpha((unsigned char) * p))
+      continue;
+    if (isdigit((unsigned char) * p))
+      continue;
+    switch (*p) {
+    case ' ':
+    case '\t':
+    case '!':
+    case '#':
+    case '$':
+    case '%':
+    case '&':
+    case '\'':
+    case '*':
+    case '+':
+    case '-':
+    case '/':
+    case '=':
+    case '?':
+    case '^':
+    case '_':
+    case '`':
+    case '{':
+    case '|':
+    case '}':
+    case '~':
+      break;
+    default:
+      return 0;
+    }
+  }
+  
+  return 1;
+}
+
+static int mailimf_mailbox_write(FILE * f, int * col,
+                                struct mailimf_mailbox * mb)
+{
+  int r;
+  int do_fold;
+  
+#if 0
+  if (* col > 1) {
+    
+    if (mb->mb_display_name != NULL) {
+      if (* col + strlen(mb->mb_display_name) >= MAX_MAIL_COL) {
+        r = mailimf_string_write(f, col, "\r\n ", 3);
+        if (r != MAILIMF_NO_ERROR)
+          return r;
+#if 0
+        * col = 1;
+#endif
+      }
+    }
+  }
+#endif
+  
+  if (mb->mb_display_name) {
+
+    if (is_atext(mb->mb_display_name)) {
+      r = mailimf_header_string_write(f, col, mb->mb_display_name,
+          strlen(mb->mb_display_name));
+      if (r != MAILIMF_NO_ERROR)
+        return r;
+    }
+    else {
+      if (mb->mb_display_name != NULL) {
+        if (* col + strlen(mb->mb_display_name) >= MAX_MAIL_COL) {
+          r = mailimf_string_write(f, col, "\r\n ", 3);
+          if (r != MAILIMF_NO_ERROR)
+            return r;
+        }
+      }
+      
+      if (strlen(mb->mb_display_name) > MAX_VALID_IMF_LINE / 2)
+        return MAILIMF_ERROR_INVAL;
+      
+      r = mailimf_quoted_string_write(f, col, mb->mb_display_name,
+          strlen(mb->mb_display_name));
+      if (r != MAILIMF_NO_ERROR)
+        return r;
+    }
+    
+    do_fold = 0;
+    if (* col > 1) {
+      
+      if (* col + strlen(mb->mb_addr_spec) + 3 >= MAX_MAIL_COL) {
+       r = mailimf_string_write(f, col, "\r\n ", 3);
+       if (r != MAILIMF_NO_ERROR)
+         return r;
+#if 0
+       * col = 1;
+#endif
+        do_fold = 1;
+      }
+    }
+    
+    if (do_fold)
+      r = mailimf_string_write(f, col, "<", 1);
+    else
+      r = mailimf_string_write(f, col, " <", 2);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+
+    r = mailimf_string_write(f, col, mb->mb_addr_spec,
+        strlen(mb->mb_addr_spec));
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+
+    r = mailimf_string_write(f, col, ">", 1);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+  else {
+    if (* col + strlen(mb->mb_addr_spec) >= MAX_MAIL_COL) {
+      r = mailimf_string_write(f, col, "\r\n ", 3);
+      if (r != MAILIMF_NO_ERROR)
+        return r;
+    }
+    
+    r = mailimf_string_write(f, col,
+        mb->mb_addr_spec, strlen(mb->mb_addr_spec));
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_comments_write(FILE * f, int * col,
+                                 struct mailimf_comments * comments)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Comments: ", 10);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_header_string_write(f, col,
+      comments->cm_value, strlen(comments->cm_value));
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_optional_field_write(FILE * f, int * col,
+                                       struct mailimf_optional_field * field)
+{
+  int r;
+
+  if (strlen(field->fld_name) + 2 > MAX_VALID_IMF_LINE)
+    return MAILIMF_ERROR_INVAL;
+  
+  r = mailimf_string_write(f, col, field->fld_name, strlen(field->fld_name));
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, ": ", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_header_string_write(f, col, field->fld_value,
+      strlen(field->fld_value));
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+#if 0
+  /* XXX parsing debug */
+  mailimf_string_write(f, col, " (X)", 4);
+#endif
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_keywords_write(FILE * f, int * col,
+                                 struct mailimf_keywords * keywords)
+{
+  int r;
+  clistiter * cur;
+  int first;
+  
+  r = mailimf_string_write(f, col, "Keywords: ", 10);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  first = TRUE;
+
+  for(cur = clist_begin(keywords->kw_list) ; cur != NULL ;
+      cur = clist_next(cur)) {
+    char * keyword;
+    size_t len;
+
+    keyword = clist_content(cur);
+    len = strlen(keyword);
+
+    if (!first) {
+      r = mailimf_string_write(f, col, ", ", 2);
+      if (r != MAILIMF_NO_ERROR)
+       return r;
+    }
+    else {
+      first = FALSE;
+    }
+
+#if 0
+    if (* col > 1) {
+      
+      if (* col + len >= MAX_MAIL_COL) {
+       r = mailimf_string_write(f, col, "\r\n ", 3);
+       if (r != MAILIMF_NO_ERROR)
+         return r;
+#if 0
+       * col = 1;
+#endif
+      }
+    }
+#endif
+
+    r = mailimf_header_string_write(f, col, keyword, len);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+#if 0
+static int mailimf_delivering_info_write(FILE * f, int * col,
+                                        struct mailimf_delivering_info * info)
+{
+  clistiter * cur;
+  int r;
+
+  for(cur = clist_begin(info->received_fields) ;
+      cur != NULL ; cur = cur->next) {
+    struct mailimf_trace_resent_fields * field;
+
+    field = cur->data;
+
+    r = mailimf_trace_resent_fields_write(f, col, field);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int
+mailimf_trace_resent_fields_write(FILE * f, int * col,
+                                 struct mailimf_trace_resent_fields * field)
+{
+  int r;
+
+  if (field->return_path != NULL) {
+    r = mailimf_return_write(f, col, field->return_path);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  if (field->resent_fields != NULL) {
+    r = mailimf_resent_fields_write(f, col, field->resent_fields);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+static int mailimf_return_write(FILE * f, int * col,
+                               struct mailimf_return * return_path)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Return-Path: ", 13);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_path_write(f, col, return_path->ret_path);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_path_write(FILE * f, int * col,
+                             struct mailimf_path * path)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "<", 1);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, path->pt_addr_spec,
+      strlen(path->pt_addr_spec));
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, ">", 1);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  return MAILIMF_NO_ERROR;
+}
+
+#if 0
+static int mailimf_resent_fields_write(FILE * f, int * col,
+                                      struct mailimf_resent_fields_list *
+                                      resent_fields)
+{
+  clistiter * cur;
+  int r;
+
+  for(cur = clist_begin(resent_fields->list) ; cur != NULL ; cur = cur->next) {
+    struct mailimf_resent_field * field;
+
+    field = cur->data;
+
+    r = mailimf_resent_field_write(f, col, field);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+
+static int mailimf_resent_field_write(FILE * f, int * col,
+                                     struct mailimf_resent_field *
+                                     resent_field)
+{
+  int r;
+
+  switch (resent_field->type) {
+  case MAILIMF_RESENT_FIELD_DATE:
+    r = mailimf_resent_date_write(f, col, resent_field->resent_date);
+    break;
+
+  case MAILIMF_RESENT_FIELD_FROM:
+    r = mailimf_resent_from_write(f, col, resent_field->resent_from);
+    break;
+
+  case MAILIMF_RESENT_FIELD_SENDER:
+    r = mailimf_resent_sender_write(f, col, resent_field->resent_sender);
+    break;
+
+  case MAILIMF_RESENT_FIELD_TO:
+    r = mailimf_resent_to_write(f, col, resent_field->resent_to);
+    break;
+
+  case MAILIMF_RESENT_FIELD_CC:
+    r = mailimf_resent_cc_write(f, col, resent_field->resent_cc);
+    break;
+
+  case MAILIMF_RESENT_FIELD_BCC:
+    r = mailimf_resent_bcc_write(f, col, resent_field->resent_bcc);
+    break;
+
+  case MAILIMF_RESENT_FIELD_MSG_ID:
+    r = mailimf_resent_msg_id_write(f, col, resent_field->resent_msg_id);
+    break;
+  default:
+    r = MAILIMF_ERROR_INVAL;
+    break;
+  }
+
+
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  return MAILIMF_NO_ERROR;
+}
+#endif
+
+static int mailimf_resent_date_write(FILE * f, int * col,
+                                    struct mailimf_orig_date * date)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Resent-Date: ", 13);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_date_time_write(f, col, date->dt_date_time);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_resent_from_write(FILE * f, int * col,
+                                    struct mailimf_from * from)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Resent-From: ", 13);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_mailbox_list_write(f, col, from->frm_mb_list);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_resent_sender_write(FILE * f, int * col,
+                                      struct mailimf_sender * sender)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Resent-Sender: ", 15);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_mailbox_write(f, col, sender->snd_mb);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+static int mailimf_resent_to_write(FILE * f, int * col,
+                                  struct mailimf_to * to)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Resent-To: ", 11);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_address_list_write(f, col, to->to_addr_list);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_resent_cc_write(FILE * f, int * col,
+                                  struct mailimf_cc * cc)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Resent-Cc: ", 11);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_address_list_write(f, col, cc->cc_addr_list);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int mailimf_resent_bcc_write(FILE * f, int * col,
+                                   struct mailimf_bcc * bcc)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Resent-Bcc: ", 12);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  if (bcc->bcc_addr_list != NULL) {
+    r =  mailimf_address_list_write(f, col, bcc->bcc_addr_list);
+    if (r != MAILIMF_NO_ERROR)
+      return r;
+  }
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
+
+
+static int
+mailimf_resent_msg_id_write(FILE * f, int * col,
+                           struct mailimf_message_id * message_id)
+{
+  int r;
+
+  r = mailimf_string_write(f, col, "Resent-Message-ID: ", 19);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "<", 1);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col,
+      message_id->mid_value, strlen(message_id->mid_value));
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, ">", 1);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+
+  r = mailimf_string_write(f, col, "\r\n", 2);
+  if (r != MAILIMF_NO_ERROR)
+    return r;
+#if 0
+  * col = 0;
+#endif
+
+  return MAILIMF_NO_ERROR;
+}
diff --git a/src/plugins/mailmbox/mailimf_write.h b/src/plugins/mailmbox/mailimf_write.h
new file mode 100644 (file)
index 0000000..e8584a6
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef MAILIMF_WRITE_H
+
+#define MAILIMF_WRITE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include "mailimf_types.h"
+
+/*
+  mailimf_string_write writes a string to a given stream
+  
+  @param f is the stream
+  @param col (* col) is the column number where we will start to
+    write the text, the ending column will be stored in (* col)
+  @param str is the string to write
+*/
+
+int mailimf_string_write(FILE * f, int * col,
+    const char * str, size_t length);
+
+
+/*
+  mailimf_fields_write writes the fields to a given stream
+  
+  @param f is the stream
+  @param col (* col) is the column number where we will start to
+    write the text, the ending column will be stored in (* col)
+  @param fields is the fields to write
+*/
+
+int mailimf_fields_write(FILE * f, int * col,
+    struct mailimf_fields * fields);
+
+
+/*
+  mailimf_envelope_fields_write writes only some fields to a given stream
+  
+  @param f is the stream
+  @param col (* col) is the column number where we will start to
+    write the text, the ending column will be stored in (* col)
+  @param fields is the fields to write
+*/
+
+int mailimf_envelope_fields_write(FILE * f, int * col,
+    struct mailimf_fields * fields);
+
+
+/*
+  mailimf_field_write writes a field to a given stream
+  
+  @param f is the stream
+  @param col (* col) is the column number where we will start to
+    write the text, the ending column will be stored in (* col)
+  @param field is the field to write
+*/
+
+int mailimf_field_write(FILE * f, int * col,
+    struct mailimf_field * field);
+
+/*
+  mailimf_quoted_string_write writes a string that is quoted
+  to a given stream
+  
+  @param f is the stream
+  @param col (* col) is the column number where we will start to
+    write the text, the ending column will be stored in (* col)
+  @param string is the string to quote and write
+*/
+
+int mailimf_quoted_string_write(FILE * f, int * col,
+    const char * string, size_t len);
+
+int mailimf_address_list_write(FILE * f, int * col,
+    struct mailimf_address_list * addr_list);
+
+int mailimf_mailbox_list_write(FILE * f, int * col,
+    struct mailimf_mailbox_list * mb_list);
+
+/*
+  mailimf_header_string_write writes a header value and fold the header
+    if needed.
+  
+  @param f is the stream
+  @param col (* col) is the column number where we will start to
+    write the text, the ending column will be stored in (* col)
+  @param str is the string to write
+*/
+
+int mailimf_header_string_write(FILE * f, int * col,
+    const char * str, size_t length);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/plugins/mailmbox/maillock.c b/src/plugins/mailmbox/maillock.c
new file mode 100644 (file)
index 0000000..569bc94
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id $
+ */
+
+#include "maillock.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <string.h>
+
+/* ********************************************************************** */
+
+/* lock primitives */
+
+/* the lock code is modified from the dot lock file code from mail.local.c */
+
+/*
+                            SENDMAIL LICENSE
+
+The following license terms and conditions apply, unless a different
+license is obtained from Sendmail, Inc., 6425 Christie Ave, Fourth Floor,
+Emeryville, CA 94608, or by electronic mail at license@sendmail.com.
+
+License Terms:
+
+Use, Modification and Redistribution (including distribution of any
+modified or derived work) in source and binary forms is permitted only if
+each of the following conditions is met:
+
+1. Redistributions qualify as "freeware" or "Open Source Software" under
+   one of the following terms:
+
+   (a) Redistributions are made at no charge beyond the reasonable cost of
+       materials and delivery.
+
+   (b) Redistributions are accompanied by a copy of the Source Code or by an
+       irrevocable offer to provide a copy of the Source Code for up to three
+       years at the cost of materials and delivery.  Such redistributions
+       must allow further use, modification, and redistribution of the Source
+       Code under substantially the same terms as this license.  For the
+       purposes of redistribution "Source Code" means the complete compilable
+       and linkable source code of sendmail including all modifications.
+
+2. Redistributions of source code must retain the copyright notices as they
+   appear in each source code file, these license terms, and the
+   disclaimer/limitation of liability set forth as paragraph 6 below.
+
+3. Redistributions in binary form must reproduce the Copyright Notice,
+   these license terms, and the disclaimer/limitation of liability set
+   forth as paragraph 6 below, in the documentation and/or other materials
+   provided with the distribution.  For the purposes of binary distribution
+   the "Copyright Notice" refers to the following language:
+   "Copyright (c) 1998-2002 Sendmail, Inc.  All rights reserved."
+
+4. Neither the name of Sendmail, Inc. nor the University of California nor
+   the names of their contributors may be used to endorse or promote
+   products derived from this software without specific prior written
+   permission.  The name "sendmail" is a trademark of Sendmail, Inc.
+
+5. All redistributions must comply with the conditions imposed by the
+   University of California on certain embedded code, whose copyright
+   notice and conditions for redistribution are as follows:
+
+   (a) Copyright (c) 1988, 1993 The Regents of the University of
+       California.  All rights reserved.
+
+   (b) Redistribution and use in source and binary forms, with or without
+       modification, are permitted provided that the following conditions
+       are met:
+
+      (i)   Redistributions of source code must retain the above copyright
+            notice, this list of conditions and the following disclaimer.
+
+      (ii)  Redistributions in binary form must reproduce the above
+            copyright notice, this list of conditions and the following
+            disclaimer in the documentation and/or other materials provided
+            with the distribution.
+
+      (iii) Neither the name of the University nor the names of its
+            contributors may be used to endorse or promote products derived
+            from this software without specific prior written permission.
+
+6. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY
+   SENDMAIL, INC. AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+   NO EVENT SHALL SENDMAIL, INC., THE REGENTS OF THE UNIVERSITY OF
+   CALIFORNIA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+*/
+
+/*
+  TODO : lock, prefer fcntl() over flock()
+         AND use dotlock code above
+*/
+
+#define LOCKTO_RM      300     /* timeout for stale lockfile removal */
+#define LOCKTO_GLOB    400     /* global timeout for lockfile creation */
+
+static int lock_common(const char * filename, int fd, short locktype)
+{
+  char lockfilename[PATH_MAX];
+  struct flock lock;
+  /* dot lock file */
+  int statfailed = 0;
+  time_t start;
+  int r;
+  int res;
+
+  lock.l_start = 0;
+  lock.l_len = 0;
+  lock.l_pid = getpid();
+  lock.l_type = locktype;
+  lock.l_whence = SEEK_SET;
+
+  r = fcntl(fd, F_SETLKW, &lock);
+  if (r < 0) {
+    /* WARNING POSIX lock could not be applied */
+  }
+
+  /* dot lock file */
+
+  if (strlen(filename) + 6 > PATH_MAX) {
+    res = -1;
+    goto unlock;
+  }
+
+  snprintf(lockfilename, PATH_MAX, "%s.lock", filename);
+
+  time(&start);
+  while (1) {
+    int fd;
+    struct stat st;
+    time_t now;
+    
+    /* global timeout */
+    time(&now);
+    if (now > start + LOCKTO_GLOB) {
+      res = -1;
+      goto unlock;
+    }
+
+    fd = open(lockfilename, O_WRONLY|O_EXCL|O_CREAT, 0);
+    if (fd >= 0) {
+      /* defeat lock checking programs which test pid */
+      write(fd, "0", 2);
+      close(fd);
+      break;
+    }
+    
+    /* libEtPan! - adds a delay of 5 seconds between each tries */
+    sleep(5);
+    
+    if (stat(lockfilename, &st) < 0) {
+      if (statfailed++ > 5) {
+       res = -1;
+       goto unlock;
+      }
+      continue;
+    }
+    statfailed = 0;
+    time(&now);
+
+    if (now < st.st_ctime + LOCKTO_RM)
+      continue;
+    
+    /* try to remove stale lockfile */
+    if (unlink(lockfilename) < 0) {
+      res = -1;
+      goto unlock;
+    }
+
+    /*
+      libEtPan! - removes this delay of 5 seconds,
+       maybe it was misplaced ?
+    */
+#if 0
+    sleep(5);
+#endif
+  }
+
+  return 0;
+
+ unlock:
+  lock.l_start = 0;
+  lock.l_len = 0;
+  lock.l_pid = getpid();
+  lock.l_type = F_UNLCK;
+  lock.l_whence = SEEK_SET;
+
+  r = fcntl(fd, F_SETLK, &lock);
+  if (r < 0) {
+    /* WARNING POSIX lock could not be applied */
+  }
+  return res;
+}
+
+static int unlock_common(const char * filename, int fd)
+{
+  char lockfilename[PATH_MAX];
+  struct flock lock;
+  int r;
+
+  if (strlen(filename) + 6 > PATH_MAX)
+    return -1;
+
+  snprintf(lockfilename, PATH_MAX, "%s.lock", filename);
+
+  unlink(lockfilename);
+
+  lock.l_start = 0;
+  lock.l_len = 0;
+  lock.l_pid = getpid();
+  lock.l_type = F_UNLCK;
+  lock.l_whence = SEEK_SET;
+
+  r = fcntl(fd, F_SETLK, &lock);
+  if (r < 0) {
+    /* WARNING POSIX lock could not be applied */
+  }
+
+  return 0;
+}
+
+int maillock_read_lock(const char * filename, int fd)
+{
+  return lock_common(filename, fd, F_RDLCK);
+}
+
+int maillock_read_unlock(const char * filename, int fd)
+{
+  return unlock_common(filename, fd);
+}
+
+int maillock_write_lock(const char * filename, int fd)
+{
+  return lock_common(filename, fd, F_WRLCK);
+}
+
+int maillock_write_unlock(const char * filename, int fd)
+{
+  return unlock_common(filename, fd);
+}
diff --git a/src/plugins/mailmbox/maillock.h b/src/plugins/mailmbox/maillock.h
new file mode 100644 (file)
index 0000000..2f64e35
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef MAILLOCK_H
+
+#define MAILLOCK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int maillock_read_lock(const char * filename, int fd);
+int maillock_read_unlock(const char * filename, int fd);
+int maillock_write_lock(const char * filename, int fd);
+int maillock_write_unlock(const char * filename, int fd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/plugins/mailmbox/mailmbox.c b/src/plugins/mailmbox/mailmbox.c
new file mode 100644 (file)
index 0000000..7cb25fd
--- /dev/null
@@ -0,0 +1,1473 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#include "mailmbox.h"
+
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include "mmapstring.h"
+#include "mailmbox_parse.h"
+#include "maillock.h"
+#include "utils.h"
+
+/*
+  http://www.qmail.org/qmail-manual-html/man5/mbox.html
+  RFC 2076
+*/
+
+#define TMPDIR "/tmp"
+
+/* mbox is a file with a corresponding filename */
+
+#define UID_HEADER "X-LibEtPan-UID:"
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+int claws_mailmbox_write_lock(struct claws_mailmbox_folder * folder)
+{
+  int r;
+
+  if (folder->mb_read_only)
+    return MAILMBOX_ERROR_READONLY;
+
+  r = maillock_write_lock(folder->mb_filename, folder->mb_fd);
+  if (r == 0)
+    return MAILMBOX_NO_ERROR;
+  else
+    return MAILMBOX_ERROR_FILE;
+}
+
+int claws_mailmbox_write_unlock(struct claws_mailmbox_folder * folder)
+{
+  int r;
+
+  r = maillock_write_unlock(folder->mb_filename, folder->mb_fd);
+  if (r == 0)
+    return MAILMBOX_NO_ERROR;
+  else
+    return MAILMBOX_ERROR_FILE;
+}
+
+int claws_mailmbox_read_lock(struct claws_mailmbox_folder * folder)
+{
+  int r;
+
+  r = maillock_read_lock(folder->mb_filename, folder->mb_fd);
+  if (r == 0)
+    return MAILMBOX_NO_ERROR;
+  else
+    return MAILMBOX_ERROR_FILE;
+}
+
+int claws_mailmbox_read_unlock(struct claws_mailmbox_folder * folder)
+{
+  int r;
+
+  r = maillock_read_unlock(folder->mb_filename, folder->mb_fd);
+  if (r == 0)
+    return MAILMBOX_NO_ERROR;
+  else
+    return MAILMBOX_ERROR_FILE;
+}
+
+
+/*
+  map the file into memory.
+  the file must be locked.
+*/
+
+int claws_mailmbox_map(struct claws_mailmbox_folder * folder)
+{
+  char * str;
+  struct stat buf;
+  int res;
+  int r;
+
+  r = stat(folder->mb_filename, &buf);
+  if (r < 0) {
+    debug_print("stat failed %d\n", r);
+    res = MAILMBOX_ERROR_FILE;
+    goto err;
+  }
+
+  if (buf.st_size == 0) {
+    folder->mb_mapping = NULL;
+    folder->mb_mapping_size = 0;
+    return MAILMBOX_NO_ERROR;
+  }
+  if (folder->mb_read_only)
+    str = (char *) mmap(NULL, buf.st_size, PROT_READ,
+                       MAP_PRIVATE, folder->mb_fd, 0);
+  else
+    str = (char *) mmap(NULL, buf.st_size, PROT_READ | PROT_WRITE,
+                       MAP_SHARED, folder->mb_fd, 0);
+  if (str == MAP_FAILED) {
+    perror("mmap");
+    debug_print("map of %lu bytes failed\n", buf.st_size);
+    res = MAILMBOX_ERROR_FILE;
+    goto err;
+  }
+  
+  folder->mb_mapping = str;
+  folder->mb_mapping_size = buf.st_size;
+
+  return MAILMBOX_NO_ERROR;
+
+ err:
+  return res;
+}
+
+/*
+  unmap the file
+*/
+
+void claws_mailmbox_unmap(struct claws_mailmbox_folder * folder)
+{
+  munmap(folder->mb_mapping, folder->mb_mapping_size);
+  folder->mb_mapping = NULL;
+  folder->mb_mapping_size = 0;
+}
+
+void claws_mailmbox_sync(struct claws_mailmbox_folder * folder)
+{
+  msync(folder->mb_mapping, folder->mb_mapping_size, MS_SYNC);
+}
+
+void claws_mailmbox_timestamp(struct claws_mailmbox_folder * folder)
+{
+  int r;
+  struct stat buf;
+
+  r = stat(folder->mb_filename, &buf);
+  if (r < 0)
+    folder->mb_mtime = (time_t) -1;
+  else
+    folder->mb_mtime = buf.st_mtime;
+}
+
+/*
+  open the file
+*/
+
+int claws_mailmbox_open(struct claws_mailmbox_folder * folder)
+{
+  int fd = -1;
+  int read_only;
+
+  if (!folder->mb_read_only) {
+    read_only = FALSE;
+    fd = open(folder->mb_filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+  }
+
+  if (folder->mb_read_only || (fd < 0)) {
+    read_only = TRUE;
+    fd = open(folder->mb_filename, O_RDONLY);
+    if (fd < 0)
+      return MAILMBOX_ERROR_FILE_NOT_FOUND;
+  }
+
+  folder->mb_fd = fd;
+  folder->mb_read_only = read_only;
+
+  return MAILMBOX_NO_ERROR;
+}
+
+/*
+  close the file
+*/
+
+void claws_mailmbox_close(struct claws_mailmbox_folder * folder)
+{
+  close(folder->mb_fd);
+  folder->mb_fd = -1;
+}
+
+
+static int claws_mailmbox_validate_lock(struct claws_mailmbox_folder * folder,
+    int (* custom_lock)(struct claws_mailmbox_folder *),
+    int (* custom_unlock)(struct claws_mailmbox_folder *))
+{
+  struct stat buf;
+  int res;
+  int r;
+
+  r = stat(folder->mb_filename, &buf);
+  if (r < 0) {
+    buf.st_mtime = (time_t) -1;
+  }
+
+  if ((buf.st_mtime != folder->mb_mtime) ||
+      ((size_t) buf.st_size != folder->mb_mapping_size)) {
+    int r;
+
+    claws_mailmbox_unmap(folder);
+    claws_mailmbox_close(folder);
+
+    r = claws_mailmbox_open(folder);
+    if (r != MAILMBOX_NO_ERROR) {
+      res = r;
+      goto err;
+    }
+
+    r = custom_lock(folder);
+    if (r != MAILMBOX_NO_ERROR) {
+      res = r;
+      goto err;
+    }
+
+    r = claws_mailmbox_map(folder);
+    if (r != MAILMBOX_NO_ERROR) {
+      res = r;
+      goto err_unlock;
+    }
+
+    r = claws_mailmbox_parse(folder);
+    if (r != MAILMBOX_NO_ERROR) {
+      res = r;
+      goto err_unlock;
+    }
+
+    folder->mb_mtime = buf.st_mtime;
+
+    return MAILMBOX_NO_ERROR;
+  }
+  else {
+    r = custom_lock(folder);
+    if (r != MAILMBOX_NO_ERROR) {
+      res = r;
+      goto err;
+    }
+  }
+
+  return MAILMBOX_NO_ERROR;
+
+ err_unlock:
+  custom_unlock(folder);
+ err:
+  return res;
+}
+
+
+int claws_mailmbox_validate_write_lock(struct claws_mailmbox_folder * folder)
+{
+  return claws_mailmbox_validate_lock(folder,
+                               claws_mailmbox_write_lock,
+                               claws_mailmbox_write_unlock);
+}
+
+
+int claws_mailmbox_validate_read_lock(struct claws_mailmbox_folder * folder)
+{
+  return claws_mailmbox_validate_lock(folder,
+                               claws_mailmbox_read_lock,
+                               claws_mailmbox_read_unlock);
+}
+
+
+/* ********************************************************************** */
+/* append messages */
+
+#define MAX_FROM_LINE_SIZE 256
+
+static inline size_t get_line(const char * line, size_t length,
+                             const char ** pnext_line, size_t * pcount)
+{
+  size_t count;
+
+  count = 0;
+
+  while (1) {
+    if (length == 0)
+      break;
+
+    if (* line == '\r') {
+      line ++;
+
+      count ++;
+      length --;
+
+      if (length > 0) {
+        if (* line == '\n') {
+          line ++;
+          
+          count ++;
+          length --;
+          
+          break;
+        }
+      }
+    }
+    else if (* line == '\n') {
+      line ++;
+
+      count ++;
+      length --;
+
+      break;
+    }
+    else {
+      line ++;
+      length --;
+      count ++;
+    }
+  }
+
+  * pnext_line = line;
+  * pcount = count;
+
+  return count;
+}
+
+/*
+  TODO : should strip \r\n if any
+  see also in write_fixed_line
+*/
+
+static inline size_t get_fixed_line_size(const char * line, size_t length,
+    const char ** pnext_line, size_t * pcount,
+    size_t * pfixed_count)
+{
+  size_t count;
+  const char * next_line;
+  size_t fixed_count;
+  
+  if (!get_line(line, length, &next_line, &count))
+    return 0;
+
+  fixed_count = count;
+  if (count >= 5) {
+    if (line[0] == 'F') {
+      if (strncmp(line, "From ", 5) == 0)
+       fixed_count ++;
+    }
+  }
+
+  * pnext_line = next_line;
+  * pcount = count;
+  * pfixed_count = fixed_count;
+
+  return count;
+}
+
+static size_t get_fixed_message_size(const char * message, size_t size,
+                                    uint32_t uid, int force_no_uid)
+{
+  size_t fixed_size;
+  size_t cur_token;
+  size_t left;
+  const char * next;
+  const char * cur;
+  int end;
+  int r;
+  uint32_t tmp_uid;
+
+  cur_token = 0;
+
+  fixed_size = 0;
+
+  /* headers */
+
+  end = FALSE;
+  while (!end) {
+    size_t begin;
+    int ignore;
+
+    ignore = FALSE;
+    begin = cur_token;
+    if (cur_token + strlen(UID_HEADER) <= size) {
+      if (message[cur_token] == 'X') {
+       if (strncasecmp(message + cur_token, UID_HEADER,
+                       strlen(UID_HEADER)) == 0) {
+         ignore = TRUE;
+       }
+      }
+    }
+
+    r = mailimf_ignore_field_parse(message, size, &cur_token);
+    switch (r) {
+    case MAILIMF_NO_ERROR:
+      if (!ignore)
+       fixed_size += cur_token - begin;
+      break;
+    case MAILIMF_ERROR_PARSE:
+    default:
+      end = TRUE;
+      break;
+    }
+  }
+
+  if (!force_no_uid) {
+    /* UID header */
+
+#if CRLF_BADNESS    
+    fixed_size += strlen(UID_HEADER " \r\n");
+#else
+    fixed_size += strlen(UID_HEADER " \n");
+#endif
+    
+    tmp_uid = uid;
+    while (tmp_uid >= 10) {
+      tmp_uid /= 10;
+      fixed_size ++;
+    }
+    fixed_size ++;
+  }
+
+  /* body */
+
+  left = size - cur_token;
+  next = message + cur_token;
+  while (left > 0) {
+    size_t count;
+    size_t fixed_count;
+
+    cur = next;
+
+    if (!get_fixed_line_size(cur, left, &next, &count, &fixed_count))
+      break;
+
+    fixed_size += fixed_count;
+    left -= count;
+  }
+
+  return fixed_size;
+}
+
+static inline char * write_fixed_line(char * str,
+    const char * line, size_t length,
+    const char ** pnext_line, size_t * pcount)
+{
+  size_t count;
+  const char * next_line;
+  
+  if (!get_line(line, length, &next_line, &count))
+    return str;
+
+  if (count >= 5) {
+    if (line[0] == 'F') {
+      if (strncmp(line, "From ", 5) == 0) {
+       * str = '>';
+       str ++;
+      }
+    }
+  }
+
+  memcpy(str, line, count);
+  
+  * pnext_line = next_line;
+  * pcount = count;
+  str += count;
+
+  return str;
+}
+
+static char * write_fixed_message(char * str,
+                                 const char * message, size_t size,
+                                 uint32_t uid, int force_no_uid)
+{
+  size_t fixed_size;
+  size_t cur_token;
+  size_t left;
+  int end;
+  int r;
+  const char * cur_src;
+  size_t numlen;
+
+  cur_token = 0;
+
+  fixed_size = 0;
+
+  /* headers */
+
+  end = FALSE;
+  while (!end) {
+    size_t begin;
+    int ignore;
+
+    ignore = FALSE;
+    begin = cur_token;
+    if (cur_token + strlen(UID_HEADER) <= size) {
+      if (message[cur_token] == 'X') {
+       if (strncasecmp(message + cur_token, UID_HEADER,
+                       strlen(UID_HEADER)) == 0) {
+         ignore = TRUE;
+       }
+      }
+    }
+
+    r = mailimf_ignore_field_parse(message, size, &cur_token);
+    switch (r) {
+    case MAILIMF_NO_ERROR:
+      if (!ignore) {
+       memcpy(str, message + begin, cur_token - begin);
+       str += cur_token - begin;
+      }
+      break;
+    case MAILIMF_ERROR_PARSE:
+    default:
+      end = TRUE;
+      break;
+    }
+  }
+
+  if (!force_no_uid) {
+    /* UID header */
+    
+    memcpy(str, UID_HEADER " ", strlen(UID_HEADER " "));
+    str += strlen(UID_HEADER " ");
+#if CRLF_BADNESS    
+    numlen = snprintf(str, 20, "%i\r\n", uid);
+#else
+    numlen = snprintf(str, 20, "%i\n", uid);
+#endif
+    str += numlen;
+  }
+
+  /* body */
+
+  cur_src = message + cur_token;
+  left = size - cur_token;
+  while (left > 0) {
+    size_t count = 0;
+    const char * next = NULL;
+
+    str = write_fixed_line(str, cur_src, left, &next, &count);
+    
+    cur_src = next;
+    left -= count;
+  }
+
+  return str;
+}
+
+#define DEFAULT_FROM_LINE "From - Wed Jun 30 21:49:08 1993\n"
+
+int
+claws_mailmbox_append_message_list_no_lock(struct claws_mailmbox_folder * folder,
+                                    carray * append_tab)
+{
+  size_t extra_size;
+  int r;
+  char from_line[MAX_FROM_LINE_SIZE] = DEFAULT_FROM_LINE;
+  struct tm time_info;
+  time_t date;
+  int res;
+  size_t old_size;
+  char * str;
+  unsigned int i;
+  size_t from_size;
+  size_t maxuid;
+  size_t left;
+  size_t crlf_count;
+
+  if (folder->mb_read_only) {
+    res = MAILMBOX_ERROR_READONLY;
+    goto err;
+  }
+
+  date = time(NULL);
+  from_size = strlen(DEFAULT_FROM_LINE);
+  if (localtime_r(&date, &time_info) != NULL)
+    from_size = strftime(from_line, MAX_FROM_LINE_SIZE, "From - %c\n", &time_info);
+
+  maxuid = /* */ folder->mb_max_uid;
+
+  extra_size = 0;
+  for(i = 0 ; i < carray_count(append_tab) ; i ++) {
+    struct claws_mailmbox_append_info * info;
+
+    info = carray_get(append_tab, i);
+    extra_size += from_size;
+    extra_size += get_fixed_message_size(info->ai_message, info->ai_size,
+        folder->mb_max_uid + i + 1,
+        folder->mb_no_uid);
+#if CRLF_BADNESS
+    extra_size += 2; /* CR LF */
+#else
+    extra_size += 1; /* CR LF */
+#endif    
+  }
+
+  left = folder->mb_mapping_size;
+  crlf_count = 0;
+  while (left >= 1) {
+    if (folder->mb_mapping[left - 1] == '\n') {
+      crlf_count ++;
+      left --;
+    }
+#if CRLF_BADNESS    
+    else if (folder->mb_mapping[left - 1] == '\r') {
+      left --;
+    }
+#endif
+    else
+      break;
+
+    if (crlf_count == 2)
+      break;
+  }
+
+  old_size = folder->mb_mapping_size;
+  claws_mailmbox_unmap(folder);
+
+  if (old_size != 0) {
+    if (crlf_count != 2)
+#if CRLF_BADNESS
+      extra_size += (2 - crlf_count) * 2;
+#else
+      /* Need the number of LFs, not CRLFs */
+      extra_size += (2 - crlf_count) * 1; /* 2 */
+#endif      
+  }
+
+  r = ftruncate(folder->mb_fd, extra_size + old_size);
+  if (r < 0) {
+    debug_print("ftruncate failed with %d\n", r);
+    claws_mailmbox_map(folder);
+    res = MAILMBOX_ERROR_FILE;
+    goto err;
+  }
+
+  r = claws_mailmbox_map(folder);
+  if (r < 0) {
+    debug_print("claws_mailmbox_map failed with %d\n", r);
+    ftruncate(folder->mb_fd, old_size);
+    return MAILMBOX_ERROR_FILE;
+  }
+
+  str = folder->mb_mapping + old_size;
+
+  if (old_size != 0) {
+    for(i = 0 ; i < 2 - crlf_count ; i ++) {
+#if CRLF_BADNESS    
+      * str = '\r';
+      str ++;
+#endif      
+      * str = '\n';
+      str ++;
+    }
+  }
+
+  for(i = 0 ; i < carray_count(append_tab) ; i ++) {
+    struct claws_mailmbox_append_info * info;
+
+    info = carray_get(append_tab, i);
+
+    memcpy(str, from_line, from_size);
+
+    str += strlen(from_line);
+
+    str = write_fixed_message(str, info->ai_message, info->ai_size,
+        folder->mb_max_uid + i + 1,
+        folder->mb_no_uid);
+
+#if CRLF_BADNESS
+    * str = '\r';
+    str ++;
+#endif    
+    * str = '\n';
+    str ++;
+  }
+
+  folder->mb_max_uid += carray_count(append_tab);
+
+  return MAILMBOX_NO_ERROR;
+
+ err:
+  return res;
+}
+
+int
+claws_mailmbox_append_message_list(struct claws_mailmbox_folder * folder,
+                            carray * append_tab)
+{
+  int r;
+  int res;
+  size_t cur_token;
+
+  r = claws_mailmbox_validate_write_lock(folder);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = claws_mailmbox_expunge_no_lock(folder);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto unlock;
+  }
+
+  cur_token = folder->mb_mapping_size;
+
+  r = claws_mailmbox_append_message_list_no_lock(folder, append_tab);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto unlock;
+  }
+
+  claws_mailmbox_sync(folder);
+
+  r = claws_mailmbox_parse_additionnal(folder, &cur_token);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto unlock;
+  }
+
+  claws_mailmbox_timestamp(folder);
+
+  claws_mailmbox_write_unlock(folder);
+
+  return MAILMBOX_NO_ERROR;
+
+ unlock:
+  claws_mailmbox_write_unlock(folder);
+ err:
+  return res;
+}
+
+int
+claws_mailmbox_append_message(struct claws_mailmbox_folder * folder,
+                       const char * data, size_t len)
+{
+  carray * tab;
+  struct claws_mailmbox_append_info * append_info;
+  int res;
+  int r;
+
+  tab = carray_new(1);
+  if (tab == NULL) {
+    res = MAILMBOX_ERROR_MEMORY;
+    goto err;
+  }
+
+  append_info = claws_mailmbox_append_info_new(data, len);
+  if (append_info == NULL) {
+    res = MAILMBOX_ERROR_MEMORY;
+    goto free_list;
+  }
+
+  r = carray_add(tab, append_info, NULL);
+  if (r < 0) {
+    res = MAILMBOX_ERROR_MEMORY;
+    goto free_append_info;
+  }
+
+  r = claws_mailmbox_append_message_list(folder, tab);
+
+  claws_mailmbox_append_info_free(append_info);
+  carray_free(tab);
+
+  return r;
+
+ free_append_info:
+  claws_mailmbox_append_info_free(append_info);
+ free_list:
+  carray_free(tab);
+ err:
+  return res;
+}
+
+/* ********************************************************************** */
+
+int claws_mailmbox_fetch_msg_no_lock(struct claws_mailmbox_folder * folder,
+                              uint32_t num, const char ** result,
+                              size_t * result_len)
+{
+  struct claws_mailmbox_msg_info * info;
+  int res;
+  chashdatum key;
+  chashdatum data;
+  int r;
+  
+  key.data = &num;
+  key.len = sizeof(num);
+  
+  r = chash_get(folder->mb_hash, &key, &data);
+  if (r < 0) {
+    res = MAILMBOX_ERROR_MSG_NOT_FOUND;
+    goto err;
+  }
+  
+  info = data.data;
+  
+  if (info->msg_deleted) {
+    res = MAILMBOX_ERROR_MSG_NOT_FOUND;
+    goto err;
+  }
+
+  * result = folder->mb_mapping + info->msg_headers;
+  * result_len = info->msg_size - info->msg_start_len;
+
+  return MAILMBOX_NO_ERROR;
+
+ err:
+  return res;
+}
+
+int claws_mailmbox_fetch_msg_headers_no_lock(struct claws_mailmbox_folder * folder,
+                                      uint32_t num, const char ** result,
+                                      size_t * result_len)
+{
+  struct claws_mailmbox_msg_info * info;
+  int res;
+  chashdatum key;
+  chashdatum data;
+  int r;
+  
+  key.data = &num;
+  key.len = sizeof(num);
+  
+  r = chash_get(folder->mb_hash, &key, &data);
+  if (r < 0) {
+    res = MAILMBOX_ERROR_MSG_NOT_FOUND;
+    goto err;
+  }
+
+  info = data.data;
+
+  if (info->msg_deleted) {
+    res = MAILMBOX_ERROR_MSG_NOT_FOUND;
+    goto err;
+  }
+
+  * result = folder->mb_mapping + info->msg_headers;
+  * result_len = info->msg_headers_len;
+
+  return MAILMBOX_NO_ERROR;
+
+ err:
+  return res;
+}
+
+int claws_mailmbox_fetch_msg(struct claws_mailmbox_folder * folder,
+                      uint32_t num, const char ** result,
+                      size_t * result_len)
+{
+  MMAPString * mmapstr;
+  int res;
+  const char * data;
+  size_t len;
+  int r;
+  size_t fixed_size;
+  char * end;
+  
+  r = claws_mailmbox_validate_read_lock(folder);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = claws_mailmbox_fetch_msg_no_lock(folder, num, &data, &len);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto unlock;
+  }
+  
+  /* size with no uid */
+  fixed_size = get_fixed_message_size(data, len, 0, 1 /* force no uid */);
+  
+#if 0
+  mmapstr = mmap_string_new_len(data, fixed_size);
+  if (mmapstr == NULL) {
+    res = MAILMBOX_ERROR_MEMORY;
+    goto unlock;
+  }
+#endif
+  mmapstr = mmap_string_sized_new(fixed_size);
+  if (mmapstr == NULL) {
+    res = MAILMBOX_ERROR_MEMORY;
+    goto unlock;
+  }
+  
+  end = write_fixed_message(mmapstr->str, data, len, 0, 1 /* force no uid */);
+  * end = '\0';
+  mmapstr->len = fixed_size;
+  
+  r = mmap_string_ref(mmapstr);
+  if (r < 0) {
+    mmap_string_free(mmapstr);
+    res = MAILMBOX_ERROR_MEMORY;
+    goto unlock;
+  }
+
+  * result = mmapstr->str;
+  * result_len = mmapstr->len;
+
+  claws_mailmbox_read_unlock(folder);
+
+  return MAILMBOX_NO_ERROR;
+
+ unlock:
+  claws_mailmbox_read_unlock(folder);
+ err:
+  return res;
+}
+
+int claws_mailmbox_fetch_msg_headers(struct claws_mailmbox_folder * folder,
+                              uint32_t num, const char ** result,
+                              size_t * result_len)
+{
+  MMAPString * mmapstr;
+  int res;
+  const char * data;
+  size_t len;
+  int r;
+  size_t fixed_size;
+  char * end;
+
+  r = claws_mailmbox_validate_read_lock(folder);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = claws_mailmbox_fetch_msg_headers_no_lock(folder, num, &data, &len);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto unlock;
+  }
+
+#if 0
+  mmapstr = mmap_string_new_len(data, len);
+  if (mmapstr == NULL) {
+    res = MAILMBOX_ERROR_MEMORY;
+    goto unlock;
+  }
+#endif
+  /* size with no uid */
+  fixed_size = get_fixed_message_size(data, len, 0, 1 /* force no uid */);
+  
+  mmapstr = mmap_string_sized_new(fixed_size);
+  if (mmapstr == NULL) {
+    res = MAILMBOX_ERROR_MEMORY;
+    goto unlock;
+  }
+  
+  end = write_fixed_message(mmapstr->str, data, len, 0, 1 /* force no uid */);
+  * end = '\0';
+  mmapstr->len = fixed_size;
+
+  r = mmap_string_ref(mmapstr);
+  if (r < 0) {
+    mmap_string_free(mmapstr);
+    res = MAILMBOX_ERROR_MEMORY;
+    goto unlock;
+  }
+
+  * result = mmapstr->str;
+  * result_len = mmapstr->len;
+
+  claws_mailmbox_read_unlock(folder);
+
+  return MAILMBOX_NO_ERROR;
+
+ unlock:
+  claws_mailmbox_read_unlock(folder);
+ err:
+  return res;
+}
+
+void claws_mailmbox_fetch_result_free(char * msg)
+{
+  mmap_string_unref(msg);
+}
+
+
+int claws_mailmbox_copy_msg_list(struct claws_mailmbox_folder * dest_folder,
+                          struct claws_mailmbox_folder * src_folder,
+                          carray * tab)
+{
+  int r;
+  int res;
+  carray * append_tab;
+  unsigned int i;
+
+  r = claws_mailmbox_validate_read_lock(src_folder);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  append_tab = carray_new(carray_count(tab));
+  if (append_tab == NULL) {
+    res = MAILMBOX_ERROR_MEMORY;
+    goto src_unlock;
+  }
+
+  for(i = 0 ; i < carray_count(tab) ; i ++) {
+    struct claws_mailmbox_append_info * append_info;
+    const char * data;
+    size_t len;
+    uint32_t uid;
+
+    uid = * ((uint32_t *) carray_get(tab, i));
+
+    r = claws_mailmbox_fetch_msg_no_lock(src_folder, uid, &data, &len);
+    if (r != MAILMBOX_NO_ERROR) {
+      res = r;
+      goto free_list;
+    }
+    
+    append_info = claws_mailmbox_append_info_new(data, len);
+    if (append_info == NULL) {
+      res = MAILMBOX_ERROR_MEMORY;
+      goto free_list;
+    }
+    
+    r = carray_add(append_tab, append_info, NULL);
+    if (r < 0) {
+      claws_mailmbox_append_info_free(append_info);
+      res = MAILMBOX_ERROR_MEMORY;
+      goto free_list;
+    }
+  }    
+
+  r = claws_mailmbox_append_message_list(dest_folder, append_tab);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto src_unlock;
+  }
+
+  for(i = 0 ; i < carray_count(append_tab) ; i ++) {
+    struct claws_mailmbox_append_info * append_info;
+
+    append_info = carray_get(append_tab, i);
+    claws_mailmbox_append_info_free(append_info);
+  }
+  carray_free(append_tab);
+
+  claws_mailmbox_read_unlock(src_folder);
+
+  return MAILMBOX_NO_ERROR;
+
+ free_list:
+  for(i = 0 ; i < carray_count(append_tab) ; i ++) {
+    struct claws_mailmbox_append_info * append_info;
+
+    append_info = carray_get(append_tab, i);
+    claws_mailmbox_append_info_free(append_info);
+  }
+  carray_free(append_tab);
+ src_unlock:
+  claws_mailmbox_read_unlock(src_folder);
+ err:
+  return res;
+}
+
+int claws_mailmbox_copy_msg(struct claws_mailmbox_folder * dest_folder,
+                     struct claws_mailmbox_folder * src_folder,
+                     uint32_t uid)
+{
+  carray * tab;
+  int res;
+  uint32_t * puid;
+  int r;
+
+  tab = carray_new(1);
+  if (tab == NULL) {
+    res = MAILMBOX_ERROR_MEMORY;
+    goto err;
+  }
+
+  puid = malloc(sizeof(* puid));
+  if (puid == NULL) {
+    res = MAILMBOX_ERROR_MEMORY;
+    goto free_array;
+  }
+  * puid = uid;
+  
+  r = claws_mailmbox_copy_msg_list(dest_folder, src_folder, tab);
+  res = r;
+
+  free(puid);
+ free_array:
+  carray_free(tab);
+ err:
+  return res;
+}
+
+static int claws_mailmbox_expunge_to_file_no_lock(char * dest_filename, int dest_fd,
+    struct claws_mailmbox_folder * folder,
+    size_t * result_size)
+{
+  int r;
+  int res;
+  unsigned long i;
+  size_t cur_offset;
+  char * dest;
+  size_t size;
+
+  size = 0;
+  for(i = 0 ; i < carray_count(folder->mb_tab) ; i ++) {
+    struct claws_mailmbox_msg_info * info;
+
+    info = carray_get(folder->mb_tab, i);
+
+    if (!info->msg_deleted) {
+      size += info->msg_size + info->msg_padding;
+
+      if (!folder->mb_no_uid) {
+       if (!info->msg_written_uid) {
+         uint32_t uid;
+        
+#if CRLF_BADNESS
+         size += strlen(UID_HEADER " \r\n");
+#else
+         size += strlen(UID_HEADER " \n");
+#endif   
+         uid = info->msg_uid;
+         while (uid >= 10) {
+           uid /= 10;
+           size ++;
+         }
+         size ++;
+       }
+      }
+    }
+  }
+
+  r = ftruncate(dest_fd, size);
+  if (r < 0) {
+    res = MAILMBOX_ERROR_FILE;
+    goto err;
+  }
+
+  if (size) {
+    dest = (char *) mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, dest_fd, 0);
+    if (dest == MAP_FAILED) {
+      res = MAILMBOX_ERROR_FILE;
+      goto err;
+    }
+  }
+
+  cur_offset = 0;
+  for(i = 0 ; i < carray_count(folder->mb_tab) ; i ++) {
+    struct claws_mailmbox_msg_info * info;
+    
+    info = carray_get(folder->mb_tab, i);
+
+    if (!info->msg_deleted) {
+      memcpy(dest + cur_offset, folder->mb_mapping + info->msg_start,
+            info->msg_headers_len + info->msg_start_len);
+      cur_offset += info->msg_headers_len + info->msg_start_len;
+      
+      if (!folder->mb_no_uid) {
+       if (!info->msg_written_uid) {
+         size_t numlen;
+         
+         memcpy(dest + cur_offset, UID_HEADER " ", strlen(UID_HEADER " "));
+         cur_offset += strlen(UID_HEADER " ");
+#if CRLF_BADNESS
+         numlen = snprintf(dest + cur_offset, size - cur_offset,
+                           "%i\r\n", info->msg_uid);
+#else
+         numlen = snprintf(dest + cur_offset, size - cur_offset,
+                           "%i\n", info->msg_uid);
+#endif                     
+         cur_offset += numlen;
+       }
+      }
+      
+      memcpy(dest + cur_offset,
+            folder->mb_mapping + info->msg_headers + info->msg_headers_len,
+            info->msg_size - (info->msg_start_len + info->msg_headers_len)
+            + info->msg_padding);
+      
+      cur_offset += info->msg_size -
+        (info->msg_start_len + info->msg_headers_len)
+       + info->msg_padding;
+    }
+  }
+  fflush(stdout);
+
+  if (size) {
+    msync(dest, size, MS_SYNC);
+    munmap(dest, size);
+  }
+
+  * result_size = size;
+
+  return MAILMBOX_NO_ERROR;
+
+ err:
+  return res;
+}
+
+int claws_mailmbox_expunge_no_lock(struct claws_mailmbox_folder * folder)
+{
+  char tmpfile[PATH_MAX];
+  int r;
+  int res;
+  int dest_fd;
+  size_t size;
+
+  if (folder->mb_read_only)
+    return MAILMBOX_ERROR_READONLY;
+
+  if (((folder->mb_written_uid >= folder->mb_max_uid) || folder->mb_no_uid) &&
+      (!folder->mb_changed)) {
+    /* no need to expunge */
+    return MAILMBOX_NO_ERROR;
+  }
+
+  snprintf(tmpfile, PATH_MAX, "%sXXXXXX", folder->mb_filename);
+  dest_fd = mkstemp(tmpfile);
+
+  if (dest_fd < 0) {
+    res = MAILMBOX_ERROR_FILE;
+    goto unlink;
+  }
+
+  r = claws_mailmbox_expunge_to_file_no_lock(tmpfile, dest_fd,
+                                      folder, &size);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto unlink;
+  }
+
+  close(dest_fd);
+
+  r = rename(tmpfile, folder->mb_filename);
+  if (r < 0) {
+    res = r;
+    goto err;
+  }
+
+  claws_mailmbox_unmap(folder);
+  claws_mailmbox_close(folder);
+
+  r = claws_mailmbox_open(folder);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = claws_mailmbox_map(folder);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+      
+  r = claws_mailmbox_parse(folder);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+  
+  claws_mailmbox_timestamp(folder);
+
+  folder->mb_changed = FALSE;
+  folder->mb_deleted_count = 0;
+  
+  return MAILMBOX_NO_ERROR;
+
+ unlink:
+  close(dest_fd);
+  unlink(tmpfile);
+ err:
+  return res;
+}
+
+int claws_mailmbox_expunge(struct claws_mailmbox_folder * folder)
+{
+  int r;
+  int res;
+
+  r = claws_mailmbox_validate_write_lock(folder);
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  r = claws_mailmbox_expunge_no_lock(folder);
+  res = r;
+
+  claws_mailmbox_write_unlock(folder);
+ err:
+  return res;
+}
+
+int claws_mailmbox_delete_msg(struct claws_mailmbox_folder * folder, uint32_t uid)
+{
+  struct claws_mailmbox_msg_info * info;
+  int res;
+  chashdatum key;
+  chashdatum data;
+  int r;
+
+  if (folder->mb_read_only) {
+    res = MAILMBOX_ERROR_READONLY;
+    goto err;
+  }
+  
+  key.data = &uid;
+  key.len = sizeof(uid);
+  
+  r = chash_get(folder->mb_hash, &key, &data);
+  if (r < 0) {
+    res = MAILMBOX_ERROR_MSG_NOT_FOUND;
+    goto err;
+  }
+
+  info = data.data;
+
+  if (info->msg_deleted) {
+    res = MAILMBOX_ERROR_MSG_NOT_FOUND;
+    goto err;
+  }
+
+  info->msg_deleted = TRUE;
+  folder->mb_changed = TRUE;
+  folder->mb_deleted_count ++;
+
+  return MAILMBOX_NO_ERROR;
+
+ err:
+  return res;
+}
+
+
+/*
+  INIT of MBOX
+
+  - open file
+  - map the file
+
+  - lock the file
+
+  - parse memory
+
+  - unlock the file
+*/
+
+int claws_mailmbox_init(const char * filename,
+                 int force_readonly,
+                 int force_no_uid,
+                 uint32_t default_written_uid,
+                 struct claws_mailmbox_folder ** result_folder)
+{
+  struct claws_mailmbox_folder * folder;
+  int r;
+  int res;
+  
+  folder = claws_mailmbox_folder_new(filename);
+  if (folder == NULL) {
+    debug_print("folder is null for %s\n", filename);
+    res = MAILMBOX_ERROR_MEMORY;
+    goto err;
+  }
+  folder->mb_no_uid = force_no_uid;
+  folder->mb_read_only = force_readonly;
+  folder->mb_written_uid = default_written_uid;
+  
+  folder->mb_changed = FALSE;
+  folder->mb_deleted_count = 0;
+  
+  r = claws_mailmbox_open(folder);
+  if (r != MAILMBOX_NO_ERROR) {
+    debug_print("folder can't be opened %d\n", r);
+    res = r;
+    goto free;
+  }
+
+  r = claws_mailmbox_map(folder);
+  if (r != MAILMBOX_NO_ERROR) {
+    debug_print("folder can't be mapped %d\n", r);
+    res = r;
+    goto close;
+  }
+
+  r = claws_mailmbox_validate_read_lock(folder);
+  if (r != MAILMBOX_NO_ERROR) {
+    debug_print("folder can't be locked %d\n", r);
+    res = r;
+    goto unmap;
+  }
+
+  claws_mailmbox_read_unlock(folder);
+
+  * result_folder = folder;
+
+  return MAILMBOX_NO_ERROR;
+
+ unmap:
+  claws_mailmbox_unmap(folder);
+ close:
+  claws_mailmbox_close(folder);
+ free:
+  claws_mailmbox_folder_free(folder);
+ err:
+  return res;
+}
+
+
+/*
+  when MBOX is DONE
+
+  - check for changes
+
+  - unmap the file
+  - close file
+*/
+
+void claws_mailmbox_done(struct claws_mailmbox_folder * folder)
+{
+  if (!folder->mb_read_only)
+    claws_mailmbox_expunge(folder);
+  
+  claws_mailmbox_unmap(folder);
+  claws_mailmbox_close(folder);
+
+  claws_mailmbox_folder_free(folder);
+}
diff --git a/src/plugins/mailmbox/mailmbox.h b/src/plugins/mailmbox/mailmbox.h
new file mode 100644 (file)
index 0000000..60d25e8
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef MAILMBOX_H
+
+#define MAILMBOX_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "mailmbox_types.h"
+
+int
+claws_mailmbox_append_message_list(struct claws_mailmbox_folder * folder,
+                            carray * append_tab);
+
+int
+claws_mailmbox_append_message(struct claws_mailmbox_folder * folder,
+                       const char * data, size_t len);
+
+int claws_mailmbox_fetch_msg(struct claws_mailmbox_folder * folder,
+                      uint32_t num, const char ** result,
+                      size_t * result_len);
+
+int claws_mailmbox_fetch_msg_headers(struct claws_mailmbox_folder * folder,
+                              uint32_t num, const char ** result,
+                              size_t * result_len);
+
+void claws_mailmbox_fetch_result_free(char * msg);
+
+int claws_mailmbox_copy_msg_list(struct claws_mailmbox_folder * dest_folder,
+                          struct claws_mailmbox_folder * src_folder,
+                          carray * tab);
+
+int claws_mailmbox_copy_msg(struct claws_mailmbox_folder * dest_folder,
+                     struct claws_mailmbox_folder * src_folder,
+                     uint32_t uid);
+
+int claws_mailmbox_expunge(struct claws_mailmbox_folder * folder);
+
+int claws_mailmbox_delete_msg(struct claws_mailmbox_folder * folder, uint32_t uid);
+
+int claws_mailmbox_init(const char * filename,
+                 int force_readonly,
+                 int force_no_uid,
+                 uint32_t default_written_uid,
+                 struct claws_mailmbox_folder ** result_folder);
+
+void claws_mailmbox_done(struct claws_mailmbox_folder * folder);
+
+/* low-level access primitives */
+
+int claws_mailmbox_write_lock(struct claws_mailmbox_folder * folder);
+
+int claws_mailmbox_write_unlock(struct claws_mailmbox_folder * folder);
+
+int claws_mailmbox_read_lock(struct claws_mailmbox_folder * folder);
+
+int claws_mailmbox_read_unlock(struct claws_mailmbox_folder * folder);
+
+
+/* memory map */
+
+int claws_mailmbox_map(struct claws_mailmbox_folder * folder);
+
+void claws_mailmbox_unmap(struct claws_mailmbox_folder * folder);
+
+void claws_mailmbox_sync(struct claws_mailmbox_folder * folder);
+
+
+/* open & close file */
+
+int claws_mailmbox_open(struct claws_mailmbox_folder * folder);
+
+void claws_mailmbox_close(struct claws_mailmbox_folder * folder);
+
+
+/* validate cache */
+
+int claws_mailmbox_validate_write_lock(struct claws_mailmbox_folder * folder);
+
+int claws_mailmbox_validate_read_lock(struct claws_mailmbox_folder * folder);
+
+
+/* fetch message */
+
+int claws_mailmbox_fetch_msg_no_lock(struct claws_mailmbox_folder * folder,
+                              uint32_t num, const char ** result,
+                              size_t * result_len);
+
+int claws_mailmbox_fetch_msg_headers_no_lock(struct claws_mailmbox_folder * folder,
+                                      uint32_t num, const char ** result,
+                                      size_t * result_len);
+
+/* append message */
+
+int
+claws_mailmbox_append_message_list_no_lock(struct claws_mailmbox_folder * folder,
+                                    carray * append_tab);
+
+int claws_mailmbox_expunge_no_lock(struct claws_mailmbox_folder * folder);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/plugins/mailmbox/mailmbox_folder.c b/src/plugins/mailmbox/mailmbox_folder.c
new file mode 100644 (file)
index 0000000..8e6c123
--- /dev/null
@@ -0,0 +1,1142 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2003 Hiroyuki Yamamoto
+ *
+ * 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 3 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.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#  include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#undef MEASURE_TIME
+
+#ifdef MEASURE_TIME
+#  include <sys/time.h>
+#endif
+
+#include "folder.h"
+#include "procmsg.h"
+#include "procheader.h"
+#include "statusbar.h"
+#include "utils.h"
+#include "gtkutils.h"
+#include "localfolder.h"
+#include "mailmbox.h"
+#include "mailmbox_folder.h"
+#include "mailmbox_parse.h"
+
+#define MAILMBOX_CACHE_DIR           "mailmboxcache"
+
+static Folder *s_claws_mailmbox_folder_new(const gchar *name, const gchar *path);
+
+static void claws_mailmbox_folder_destroy(Folder *folder);
+
+static FolderItem *claws_mailmbox_folder_item_new(Folder *folder);
+
+static void claws_mailmbox_folder_item_destroy(Folder *folder, FolderItem *_item);
+
+static gchar *claws_mailmbox_item_get_path(Folder *folder, FolderItem *item);
+
+static gint claws_mailmbox_get_num_list(Folder *folder, FolderItem *item,
+    GSList **list, gboolean *old_uids_valid);
+
+static MsgInfo *claws_mailmbox_get_msginfo(Folder *folder,
+    FolderItem *item, gint num);
+
+static GSList *claws_mailmbox_get_msginfos(Folder *folder, FolderItem *item,
+    GSList *msgnum_list);
+
+static gchar *s_claws_mailmbox_fetch_msg(Folder *folder, FolderItem *item, gint num);
+
+static gint claws_mailmbox_add_msg(Folder *folder, FolderItem *dest,
+    const gchar *file, MsgFlags *flags);
+
+static gint claws_mailmbox_add_msgs(Folder *folder, FolderItem *dest,
+    GSList *file_list, 
+    GHashTable *relation);
+
+static gint s_claws_mailmbox_copy_msg(Folder *folder,
+    FolderItem *dest, MsgInfo *msginfo);
+
+static gint claws_mailmbox_copy_msgs(Folder *folder, FolderItem *dest, 
+    MsgInfoList *msglist, GHashTable *relation);
+
+static gint claws_mailmbox_remove_msg(Folder *folder, FolderItem *item, gint num);
+static gint claws_mailmbox_remove_msgs( Folder *folder, FolderItem *item, MsgInfoList *msglist, GRelation *relation );
+static gint claws_mailmbox_remove_all_msg(Folder *folder, FolderItem *item);
+
+static FolderItem *claws_mailmbox_create_folder(Folder *folder, FolderItem *parent,
+    const gchar *name);
+
+static gboolean claws_mailmbox_scan_required(Folder *folder, FolderItem *_item);
+
+static gint claws_mailmbox_rename_folder(Folder *folder,
+    FolderItem *item, const gchar *name);
+
+static gint claws_mailmbox_remove_folder(Folder *folder, FolderItem *item);
+
+static gint claws_mailmbox_create_tree(Folder *folder);
+
+static gint claws_mailmbox_folder_item_close(Folder *folder, FolderItem *item);
+
+static FolderClass claws_mailmbox_class;
+
+static gchar * get_cache_dir(void)
+{
+       static gchar *mbox_cache_dir = NULL;
+
+       if (!mbox_cache_dir)
+               mbox_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+                                            MAILMBOX_CACHE_DIR, NULL);
+
+       return mbox_cache_dir;
+}
+
+
+FolderClass *claws_mailmbox_get_class(void)
+{
+       if (claws_mailmbox_class.idstr == NULL) {
+               claws_mailmbox_class.type = F_MBOX;
+               claws_mailmbox_class.idstr = "mailmbox";
+               claws_mailmbox_class.uistr = "mbox";
+
+               /* Folder functions */
+               claws_mailmbox_class.new_folder = s_claws_mailmbox_folder_new;
+               claws_mailmbox_class.destroy_folder = claws_mailmbox_folder_destroy;
+               claws_mailmbox_class.set_xml = folder_local_set_xml;
+               claws_mailmbox_class.get_xml = folder_local_get_xml;
+               claws_mailmbox_class.create_tree = claws_mailmbox_create_tree;
+
+               /* FolderItem functions */
+               claws_mailmbox_class.item_new = claws_mailmbox_folder_item_new;
+               claws_mailmbox_class.item_destroy = claws_mailmbox_folder_item_destroy;
+               claws_mailmbox_class.item_get_path = claws_mailmbox_item_get_path;
+               claws_mailmbox_class.create_folder = claws_mailmbox_create_folder;
+               claws_mailmbox_class.rename_folder = claws_mailmbox_rename_folder;
+               claws_mailmbox_class.remove_folder = claws_mailmbox_remove_folder;
+               claws_mailmbox_class.close = claws_mailmbox_folder_item_close;
+               claws_mailmbox_class.get_num_list = claws_mailmbox_get_num_list;
+               claws_mailmbox_class.scan_required = claws_mailmbox_scan_required;
+
+               /* Message functions */
+               claws_mailmbox_class.get_msginfo = claws_mailmbox_get_msginfo;
+               claws_mailmbox_class.get_msginfos = claws_mailmbox_get_msginfos;
+               claws_mailmbox_class.fetch_msg = s_claws_mailmbox_fetch_msg;
+               claws_mailmbox_class.add_msg = claws_mailmbox_add_msg;
+               claws_mailmbox_class.add_msgs = claws_mailmbox_add_msgs;
+               claws_mailmbox_class.copy_msg = s_claws_mailmbox_copy_msg;
+               claws_mailmbox_class.copy_msgs = claws_mailmbox_copy_msgs;
+               claws_mailmbox_class.remove_msg = claws_mailmbox_remove_msg;
+               claws_mailmbox_class.remove_msgs = claws_mailmbox_remove_msgs;
+               claws_mailmbox_class.remove_all_msg = claws_mailmbox_remove_all_msg;
+       }
+       return &claws_mailmbox_class;
+}
+
+
+static void claws_mailmbox_folder_init(Folder *folder,
+    const gchar *name, const gchar *path)
+{
+       folder_local_folder_init(folder, name, path);
+}
+
+static Folder *s_claws_mailmbox_folder_new(const gchar *name, const gchar *path)
+{
+       Folder *folder;
+
+       folder = (Folder *)g_new0(MAILMBOXFolder, 1);
+       folder->klass = &claws_mailmbox_class;
+        claws_mailmbox_folder_init(folder, name, path);
+        
+       return folder;
+}
+
+static void claws_mailmbox_folder_destroy(Folder *folder)
+{
+       folder_local_folder_destroy(LOCAL_FOLDER(folder));
+}
+
+typedef struct _MAILMBOXFolderItem     MAILMBOXFolderItem;
+struct _MAILMBOXFolderItem
+{
+       FolderItem item;
+        guint old_max_uid;
+        struct claws_mailmbox_folder * mbox;
+};
+
+static FolderItem *claws_mailmbox_folder_item_new(Folder *folder)
+{
+       MAILMBOXFolderItem *item;
+       
+       item = g_new0(MAILMBOXFolderItem, 1);
+       item->mbox = NULL;
+        item->old_max_uid = 0;
+
+       return (FolderItem *)item;
+}
+
+#define MAX_UID_FILE "max-uid"
+
+static void read_max_uid_value(FolderItem *item, guint * pmax_uid)
+{
+        gchar * path;
+        gchar * file;
+        FILE * f;
+        guint max_uid;
+        size_t r;
+        
+       path = folder_item_get_path(item);
+       file = g_strconcat(path, G_DIR_SEPARATOR_S, MAX_UID_FILE, NULL);
+       g_free(path);
+        
+        f = fopen(file, "r");
+        g_free(file);
+        if (f == NULL)
+                return;
+        r = fread(&max_uid, sizeof(max_uid), 1, f);
+        if (r == 0) {
+                fclose(f);
+                return;
+        }
+        
+        fclose(f);
+        
+        * pmax_uid = max_uid;
+}
+
+static void write_max_uid_value(FolderItem *item, guint max_uid)
+{
+        gchar * path;
+        gchar * file;
+        FILE * f;
+        size_t r;
+        
+       path = folder_item_get_path(item);
+       file = g_strconcat(path, G_DIR_SEPARATOR_S, MAX_UID_FILE, NULL);
+       g_free(path);
+        
+        f = fopen(file, "w");
+        g_free(file);
+        if (f == NULL)
+                return;
+        r = fwrite(&max_uid, sizeof(max_uid), 1, f);
+        if (r == 0) {
+                fclose(f);
+                return;
+        }
+        
+        fclose(f);
+}
+
+static void claws_mailmbox_folder_item_destroy(Folder *folder, FolderItem *_item)
+{
+       MAILMBOXFolderItem *item = (MAILMBOXFolderItem *)_item;
+
+       g_return_if_fail(item != NULL);
+        
+        if (item->mbox != NULL) {
+                write_max_uid_value(_item, item->mbox->mb_written_uid);
+                claws_mailmbox_done(item->mbox);
+        }
+       g_free(_item);
+}
+
+static gint claws_mailmbox_folder_item_close(Folder *folder, FolderItem *item_)
+{
+       MAILMBOXFolderItem *item = (MAILMBOXFolderItem *)item_;
+
+       g_return_val_if_fail(folder->klass->type == F_MBOX, -1);
+       g_return_val_if_fail(item != NULL, -1);
+       g_return_val_if_fail(item->mbox != NULL, -1);
+
+       return -claws_mailmbox_expunge(item->mbox);
+}
+
+static void claws_mailmbox_folder_create_parent(const gchar * path)
+{
+       if (!is_file_exist(path)) {
+               gchar * new_path;
+
+               new_path = g_path_get_dirname(path);
+               if (new_path[strlen(new_path) - 1] == G_DIR_SEPARATOR)
+                       new_path[strlen(new_path) - 1] = '\0';
+
+               if (!is_dir_exist(new_path))
+                       make_dir_hier(new_path);
+               g_free(new_path);
+               
+       }
+}
+
+static gchar * claws_mailmbox_folder_get_path(Folder *folder, FolderItem *item)
+{
+       gchar *folder_path;
+       gchar *path;
+
+       g_return_val_if_fail(item != NULL, NULL);
+
+       if (item->path && item->path[0] == G_DIR_SEPARATOR) {
+               claws_mailmbox_folder_create_parent(item->path);
+               return g_strdup(item->path);
+       }
+
+       folder_path = g_strdup(LOCAL_FOLDER(item->folder)->rootpath);
+       g_return_val_if_fail(folder_path != NULL, NULL);
+
+       if (folder_path[0] == G_DIR_SEPARATOR) {
+               if (item->path) {
+                       path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
+                                          item->path, NULL);
+               }
+               else
+                       path = g_strdup(folder_path);
+       } else {
+               if (item->path)
+                       path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
+                                          folder_path, G_DIR_SEPARATOR_S,
+                                          item->path, NULL);
+               else
+                       path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
+                                          folder_path, NULL);
+       }
+
+       g_free(folder_path);
+       
+       claws_mailmbox_folder_create_parent(path);
+
+       return path;
+}
+
+static int claws_mailmbox_item_sync(FolderItem *_item, int validate_uid)
+{
+       MAILMBOXFolderItem *item = (MAILMBOXFolderItem *)_item;
+        int r;
+
+        if (item->mbox == NULL) {
+                guint written_uid;
+                gchar * path;
+                
+                written_uid = 0;
+                read_max_uid_value(_item, &written_uid);
+                path = claws_mailmbox_folder_get_path(_item->folder, _item);
+                r = claws_mailmbox_init(path, 0, 0, written_uid, &item->mbox);
+               debug_print("init %d: %p\n", r, item->mbox);
+                g_free(path);
+                if (r != MAILMBOX_NO_ERROR)
+                        return -1;
+        }
+
+        if (!validate_uid) {
+                r = claws_mailmbox_validate_read_lock(item->mbox);
+                if (r != MAILMBOX_NO_ERROR) {
+                       debug_print("read lock: %d\n", r);
+                        goto err;
+                }
+                
+                claws_mailmbox_read_unlock(item->mbox);
+        }
+        else {
+                r = claws_mailmbox_validate_write_lock(item->mbox);
+                if (r != MAILMBOX_NO_ERROR) {
+                       debug_print("write lock: %d\n", r);
+                        goto err;
+                }
+                
+                if (item->mbox->mb_written_uid < item->mbox->mb_max_uid) {
+                        r = claws_mailmbox_expunge_no_lock(item->mbox);
+                        if (r != MAILMBOX_NO_ERROR)
+                                goto unlock;
+                }
+                claws_mailmbox_write_unlock(item->mbox);
+        }
+        
+        return 0;
+        
+ unlock:
+        claws_mailmbox_write_unlock(item->mbox);
+ err:
+        return -1;
+}
+
+static struct claws_mailmbox_folder * get_mbox(FolderItem *_item, int validate_uid)
+{
+       MAILMBOXFolderItem *item = (MAILMBOXFolderItem *)_item;
+        
+        claws_mailmbox_item_sync(_item, validate_uid);
+        
+        return item->mbox;
+}
+
+static gint claws_mailmbox_get_num_list(Folder *folder, FolderItem *item,
+    GSList **list, gboolean *old_uids_valid)
+{
+       gint nummsgs = 0;
+        guint i;
+        struct claws_mailmbox_folder * mbox;
+
+       g_return_val_if_fail(item != NULL, -1);
+
+       debug_print("mbox_get_last_num(): Scanning %s ...\n", item->path);
+        
+       *old_uids_valid = TRUE;
+        
+        mbox = get_mbox(item, 1);
+        if (mbox == NULL)
+                return -1;
+        
+        for(i = 0 ; i < carray_count(mbox->mb_tab) ; i ++) {
+                struct claws_mailmbox_msg_info * msg;
+                
+                msg = carray_get(mbox->mb_tab, i);
+                if (msg != NULL) {
+                       *list = g_slist_prepend(*list,
+                            GINT_TO_POINTER(msg->msg_uid));
+                        nummsgs ++;
+                }
+        }
+        
+       return nummsgs;
+}
+
+static gchar *s_claws_mailmbox_fetch_msg(Folder *folder, FolderItem *item, gint num)
+{
+       gchar *path;
+       gchar *file;
+        int r;
+        struct claws_mailmbox_folder * mbox;
+        const char * data;
+        size_t len;
+        FILE * f;
+        mode_t old_mask;
+
+       g_return_val_if_fail(item != NULL, NULL);
+       g_return_val_if_fail(num > 0, NULL);
+        
+        mbox = get_mbox(item, 0);
+        if (mbox == NULL)
+                return NULL;
+
+       path = folder_item_get_path(item);
+       if (!is_dir_exist(path))
+               make_dir_hier(path);
+       file = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
+       g_free(path);
+       if (is_file_exist(file)) {
+               return file;
+       }
+        
+        r = claws_mailmbox_fetch_msg(mbox, num, &data, &len);
+        if (r != MAILMBOX_NO_ERROR)
+                goto free;
+        
+        old_mask = umask(0077);
+        f = fopen(file, "w");
+        umask(old_mask);
+        if (f == NULL)
+                goto free;
+        
+        r = fwrite(data, 1, len, f);
+        if (r == 0)
+                goto close;
+        
+        fclose(f);
+        
+       return file;
+        
+ close:
+        fclose(f);
+        unlink(file);
+ free:
+        free(file);
+        return NULL;
+}
+
+static MsgInfo *claws_mailmbox_parse_msg(guint uid,
+    const char * data, size_t len, FolderItem *_item)
+{
+       MsgInfo *msginfo;
+       MsgFlags flags;
+        struct claws_mailmbox_folder * mbox;
+        chashdatum key;
+        chashdatum value;
+        struct claws_mailmbox_msg_info * info;
+        int r;
+       MAILMBOXFolderItem * item = (MAILMBOXFolderItem *)_item;
+        
+       flags.perm_flags = MSG_NEW|MSG_UNREAD;
+       flags.tmp_flags = 0;
+
+       g_return_val_if_fail(item != NULL, NULL);
+       g_return_val_if_fail(data != NULL, NULL);
+
+       if (_item->stype == F_QUEUE) {
+               MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
+       } else if (_item->stype == F_DRAFT) {
+               MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
+       }
+
+        mbox = item->mbox;
+        
+        key.data = (char *) &uid;
+        key.len = sizeof(uid);
+        
+        r = chash_get(mbox->mb_hash, &key, &value);
+        if (r < 0)
+                return NULL;
+        
+        info = (struct claws_mailmbox_msg_info *) value.data;
+        
+        msginfo = procheader_parse_str(data, flags, FALSE, FALSE);
+       if (!msginfo) return NULL;
+
+       msginfo->msgnum = uid;
+       msginfo->folder = _item;
+        msginfo->size = (goffset)(info->msg_size - info->msg_start_len);
+
+       return msginfo;
+}
+
+static MsgInfo *claws_mailmbox_get_msginfo(Folder *folder,
+    FolderItem *item, gint num)
+{
+       MsgInfo *msginfo;
+        int r;
+        const char * data;
+        size_t len;
+        struct claws_mailmbox_folder * mbox;
+
+       g_return_val_if_fail(item != NULL, NULL);
+       g_return_val_if_fail(num > 0, NULL);
+
+        mbox = get_mbox(item, 0);
+        if (mbox == NULL)
+                goto err;
+
+        r = claws_mailmbox_validate_read_lock(mbox);
+        if (r != MAILMBOX_NO_ERROR)
+                goto err;
+        
+        r = claws_mailmbox_fetch_msg_headers_no_lock(mbox, num, &data, &len);
+        if (r != MAILMBOX_NO_ERROR)
+                goto unlock;
+        
+       msginfo = claws_mailmbox_parse_msg(num, data, len, item);
+       if (!msginfo)
+                goto unlock;
+
+        claws_mailmbox_read_unlock(mbox);
+
+       return msginfo;
+
+ unlock:
+        claws_mailmbox_read_unlock(mbox);
+ err:
+        return NULL;
+}
+
+static GSList *claws_mailmbox_get_msginfos(Folder *folder, FolderItem *item,
+    GSList *msgnum_list)
+{
+        int r;
+        GSList * cur;
+        GSList * ret;
+        struct claws_mailmbox_folder * mbox;
+        
+       g_return_val_if_fail(item != NULL, NULL);
+
+        mbox = get_mbox(item, 0);
+        if (mbox == NULL)
+                goto err;
+
+        r = claws_mailmbox_validate_read_lock(mbox);
+        if (r != MAILMBOX_NO_ERROR)
+                goto err;
+
+        ret = NULL;
+        
+        for (cur = msgnum_list ; cur != NULL ; cur = g_slist_next(cur)) {
+                const char * data;
+                size_t len;
+                gint num;
+                MsgInfo *msginfo;
+                
+                num = GPOINTER_TO_INT(cur->data);
+                
+                r = claws_mailmbox_fetch_msg_headers_no_lock(mbox, num, &data, &len);
+                if (r != MAILMBOX_NO_ERROR)
+                        continue;
+                
+                msginfo = claws_mailmbox_parse_msg(num, data, len, item);
+                if (!msginfo)
+                        continue;
+                
+                ret = g_slist_append(ret, msginfo);
+        }
+        
+        claws_mailmbox_read_unlock(mbox);
+
+       return ret;
+
+        claws_mailmbox_read_unlock(mbox);
+ err:
+        return NULL;
+}
+
+static gint claws_mailmbox_add_msg(Folder *folder, FolderItem *dest,
+    const gchar *file, MsgFlags *flags)
+{
+       gint ret;
+       GSList file_list;
+       MsgFileInfo fileinfo;
+
+       g_return_val_if_fail(file != NULL, -1);
+
+       fileinfo.msginfo = NULL;
+       fileinfo.file = (gchar *)file;
+       fileinfo.flags = flags;
+       file_list.data = &fileinfo;
+       file_list.next = NULL;
+
+       ret = claws_mailmbox_add_msgs(folder, dest, &file_list, NULL);
+       return ret;
+} 
+
+/* ok */
+static gint claws_mailmbox_add_msgs(Folder *folder, FolderItem *dest,
+    GSList *file_list, 
+    GHashTable *relation)
+{ 
+       GSList *cur;
+        gint last_num;
+        struct claws_mailmbox_folder * mbox;
+        carray * append_list;
+        struct claws_mailmbox_append_info append_info;
+        int r;
+
+       g_return_val_if_fail(dest != NULL, -1);
+       g_return_val_if_fail(file_list != NULL, -1);
+        
+        mbox = get_mbox(dest, 0);
+        if (mbox == NULL) {
+               debug_print("mbox not found\n");
+                return -1;
+       }
+        r = claws_mailmbox_validate_write_lock(mbox);
+        if (r != MAILMBOX_NO_ERROR) {
+               debug_print("claws_mailmbox_validate_write_lock failed with %d\n", r);
+                return -1;
+        }
+        r = claws_mailmbox_expunge_no_lock(mbox);
+        if (r != MAILMBOX_NO_ERROR) {
+               debug_print("claws_mailmbox_expunge_no_lock failed with %d\n", r);
+                goto unlock;
+        }
+
+        last_num = -1;
+
+        append_list = carray_new(1);
+        if (append_list == NULL) {
+               debug_print("append_list is null\n");
+                goto unlock;
+       }
+
+        r = carray_set_size(append_list, 1);
+        if (r < 0) {
+               debug_print("carray_set_size failed with %d\n", r);
+                goto free;
+        }
+
+        carray_set(append_list, 0, &append_info);
+        
+       for (cur = file_list; cur != NULL; cur = cur->next) {
+                int fd;
+                struct stat stat_info;
+                char * data;
+                size_t len;
+                struct claws_mailmbox_msg_info * msg;
+                size_t cur_token;
+                MsgFileInfo *fileinfo;
+                
+               fileinfo = (MsgFileInfo *)cur->data;
+                
+                fd = open(fileinfo->file, O_RDONLY);
+                if (fd == -1) {
+                       debug_print("%s couldn't be opened\n", fileinfo->file);
+                        goto err;
+                }
+
+                r = fstat(fd, &stat_info);
+                if (r < 0) {
+                       debug_print("%s couldn't be stat'ed\n", fileinfo->file);
+                        goto close;
+                }
+
+                len = stat_info.st_size;
+                data = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
+                if (data == MAP_FAILED) {
+                       debug_print("mmap failed\n");
+                        goto close;
+                }
+
+                append_info.ai_message = data;
+                append_info.ai_size = len;
+                
+                cur_token = mbox->mb_mapping_size;
+                
+                r = claws_mailmbox_append_message_list_no_lock(mbox, append_list);
+                if (r != MAILMBOX_NO_ERROR) {
+                       debug_print("claws_mailmbox_append_message_list_no_lock failed with %d\n", r);
+                        goto unmap;
+               }
+
+                munmap(data, len);
+                close(fd);
+
+                r = claws_mailmbox_parse_additionnal(mbox, &cur_token);
+                if (r != MAILMBOX_NO_ERROR) {
+                       debug_print("claws_mailmbox_parse_additionnal failed with %d\n", r);
+                        goto unlock;
+                }
+
+                msg = carray_get(mbox->mb_tab, carray_count(mbox->mb_tab) - 1);
+
+                if (relation != NULL)
+                        g_hash_table_insert(relation,
+                            fileinfo->msginfo != NULL ?
+                            (gpointer) fileinfo->msginfo :
+                            (gpointer) fileinfo,
+                            GINT_TO_POINTER(msg->msg_uid));
+                
+                last_num = msg->msg_uid;
+                
+                continue;
+                
+        unmap:
+                munmap(data, len);
+        close:
+                close(fd);
+        err:
+                continue;
+        }
+
+        claws_mailmbox_sync(mbox);
+
+        carray_free(append_list);
+        claws_mailmbox_write_unlock(mbox);
+        
+       return last_num;
+
+ free:
+        carray_free(append_list);
+ unlock:
+        claws_mailmbox_write_unlock(mbox);
+return -1;
+}
+
+static gint s_claws_mailmbox_copy_msg(Folder *folder,
+    FolderItem *dest, MsgInfo *msginfo)
+{
+       GSList msglist;
+
+       g_return_val_if_fail(msginfo != NULL, -1);
+
+       msglist.data = msginfo;
+       msglist.next = NULL;
+
+       return claws_mailmbox_copy_msgs(folder, dest, &msglist, NULL);
+}
+
+static gint claws_mailmbox_copy_msgs(Folder *folder, FolderItem *dest, 
+    MsgInfoList *msglist, GHashTable *relation)
+{
+       MsgInfo *msginfo;
+       GSList *file_list;
+       gint ret;
+        
+       g_return_val_if_fail(folder != NULL, -1);
+       g_return_val_if_fail(dest != NULL, -1);
+       g_return_val_if_fail(msglist != NULL, -1);
+
+       msginfo = (MsgInfo *)msglist->data;
+       g_return_val_if_fail(msginfo->folder != NULL, -1);
+
+       file_list = procmsg_get_message_file_list(msglist);
+       g_return_val_if_fail(file_list != NULL, -1);
+
+       ret = claws_mailmbox_add_msgs(folder, dest, file_list, relation);
+
+       procmsg_message_file_list_free(file_list);
+
+       return ret;
+}
+
+
+static gint claws_mailmbox_remove_msg(Folder *folder, FolderItem *item, gint num)
+{
+        struct claws_mailmbox_folder * mbox;
+        int r;
+        
+       g_return_val_if_fail(item != NULL, -1);
+        
+        mbox = get_mbox(item, 0);
+        if (mbox == NULL)
+                return -1;
+        
+        r = claws_mailmbox_delete_msg(mbox, num);
+        if (r != MAILMBOX_NO_ERROR)
+                return -1;
+
+       return 0;
+}
+
+static gint
+claws_mailmbox_remove_msgs( Folder *folder, FolderItem *item,
+                            MsgInfoList *msglist, GRelation *relation )
+{
+    struct claws_mailmbox_folder *mbox;
+    int r;
+    gint total = 0, curnum = 0;
+
+    g_return_val_if_fail( item!=NULL, -1 );
+    mbox=get_mbox(item,0);
+    g_return_val_if_fail( mbox!=NULL, -1 );
+
+    total = g_slist_length(msglist);
+    if (total > 100) {
+       statusbar_print_all(_("Deleting messages..."));
+    }
+
+    MsgInfoList *cur;
+    for( cur=msglist; cur; cur=cur->next )
+    {
+       MsgInfo *msginfo=(MsgInfo*) cur->data;
+       if( !msginfo )
+       {
+           continue;
+       }
+       if( MSG_IS_MOVE(msginfo->flags) && MSG_IS_MOVE_DONE(msginfo->flags) )
+       {
+           msginfo->flags.tmp_flags&=~MSG_MOVE_DONE;
+           continue;
+       }
+       if (total > 100) {
+               statusbar_progress_all(curnum, total, 100);
+               if (curnum % 100 == 0)
+                       GTK_EVENTS_FLUSH();
+               curnum++;
+       }
+      claws_mailmbox_delete_msg(mbox,msginfo->msgnum);
+    }
+
+    /* Fix for bug 1434
+     */
+    claws_mailmbox_expunge(mbox);
+    if (total > 100) {
+               statusbar_progress_all(0,0,0);
+               statusbar_pop_all();
+    }
+
+    return r;
+}
+
+
+static gint claws_mailmbox_remove_all_msg(Folder *folder, FolderItem *item)
+{
+        struct claws_mailmbox_folder * mbox;
+        int r;
+        guint i;
+        
+       g_return_val_if_fail(item != NULL, -1);
+        
+        mbox = get_mbox(item, 0);
+        if (mbox == NULL)
+                return -1;
+       
+        for(i = 0 ; i < carray_count(mbox->mb_tab) ; i ++) {
+                struct claws_mailmbox_msg_info * msg;
+                
+                msg = carray_get(mbox->mb_tab, i);
+                if (msg == NULL)
+                        continue;
+                
+                r = claws_mailmbox_delete_msg(mbox, msg->msg_uid);
+                if (r != MAILMBOX_NO_ERROR)
+                        continue;
+        }
+        
+       return 0;
+}
+
+
+static gchar * claws_mailmbox_get_new_path(FolderItem * parent, gchar * name)
+{
+       gchar * path;
+
+       if (strchr(name, G_DIR_SEPARATOR) == NULL) {
+               if (parent->path != NULL)
+                       path = g_strconcat(parent->path, ".sbd", G_DIR_SEPARATOR_S, name, NULL);
+               else
+                       path = g_strdup(name);
+       }
+       else
+               path = g_strdup(name);
+
+       return path;
+}
+
+static gchar * claws_mailmbox_get_folderitem_name(gchar * name)
+{
+       gchar * foldername;
+
+       foldername = g_path_get_basename(name);
+       
+       return foldername;
+}
+
+static FolderItem *claws_mailmbox_create_folder(Folder *folder, FolderItem *parent,
+    const gchar *name)
+{
+       gchar * path;
+       FolderItem *new_item;
+       gchar * foldername;
+
+       g_return_val_if_fail(folder != NULL, NULL);
+       g_return_val_if_fail(parent != NULL, NULL);
+       g_return_val_if_fail(name != NULL, NULL);
+
+       path = claws_mailmbox_get_new_path(parent, (gchar *) name);
+
+       foldername = claws_mailmbox_get_folderitem_name((gchar *) name);
+
+       new_item = folder_item_new(folder, foldername, path);
+       folder_item_append(parent, new_item);
+
+       if (!strcmp(name, "inbox")) {
+               new_item->stype = F_INBOX;
+               new_item->folder->inbox = new_item;
+       } else if (!strcmp(name, "outbox")) {
+               new_item->stype = F_OUTBOX;
+               new_item->folder->outbox = new_item;
+       } else if (!strcmp(name, "draft")) {
+               new_item->stype = F_DRAFT;
+               new_item->folder->draft = new_item;
+       } else if (!strcmp(name, "queue")) {
+               new_item->stype = F_QUEUE;
+               new_item->folder->queue = new_item;
+       } else if (!strcmp(name, "trash")) {
+               new_item->stype = F_TRASH;
+               new_item->folder->trash = new_item;
+       }
+       
+       g_free(foldername);
+       g_free(path);
+       
+       return new_item;
+}
+
+
+
+static gboolean claws_mailmbox_scan_required(Folder *folder, FolderItem *_item)
+{
+        struct claws_mailmbox_folder * mbox;
+       MAILMBOXFolderItem *item = (MAILMBOXFolderItem *)_item;
+        int scan_required;
+        
+       g_return_val_if_fail(folder != NULL, FALSE);
+       g_return_val_if_fail(item != NULL, FALSE);
+
+       if (item->item.path == NULL)
+               return FALSE;
+        
+        mbox = get_mbox(_item, 0);
+        if (mbox == NULL)
+                return FALSE;
+        
+        scan_required = (item->old_max_uid != item->mbox->mb_max_uid);
+        
+        item->old_max_uid = item->mbox->mb_max_uid;
+
+        return scan_required;
+}
+
+
+static gint claws_mailmbox_rename_folder(Folder *folder,
+    FolderItem *item, const gchar *name)
+{
+       gchar * path;
+       gchar * foldername;
+       FolderItem *parent;
+
+       g_return_val_if_fail(folder != NULL, -1);
+       g_return_val_if_fail(item != NULL, -1);
+       g_return_val_if_fail(item->path != NULL, -1);
+       g_return_val_if_fail(name != NULL, -1);
+       
+       parent = folder_item_parent(item);
+       g_return_val_if_fail(parent, -1);
+       
+       path = claws_mailmbox_get_new_path(parent, (gchar *) name);
+       foldername = claws_mailmbox_get_folderitem_name((gchar *) name);
+
+       if (rename(item->path, path) == -1) {
+               g_free(foldername);
+               g_free(path);
+               debug_print("Cannot rename folder item\n");
+
+               return -1;
+       }
+       else {
+               g_free(item->name);
+               g_free(item->path);
+               item->path = path;
+               item->name = foldername;
+               
+               return 0;
+       }
+}
+
+static gint claws_mailmbox_remove_folder(Folder *folder, FolderItem *item)
+{
+       g_return_val_if_fail(folder != NULL, -1);
+       g_return_val_if_fail(item != NULL, -1);
+       g_return_val_if_fail(item->path != NULL, -1);
+
+       folder_item_remove(item);
+       return 0;
+}
+
+#define MAKE_DIR_IF_NOT_EXIST(dir) \
+{ \
+       if (!is_dir_exist(dir)) { \
+               if (is_file_exist(dir)) { \
+                       debug_print("File `%s' already exists.\n" \
+                                   "Can't create folder.", dir); \
+                       return -1; \
+               } \
+               if (mkdir(dir, S_IRWXU) < 0) { \
+                       FILE_OP_ERROR(dir, "mkdir"); \
+                       return -1; \
+               } \
+               if (chmod(dir, S_IRWXU) < 0) \
+                       FILE_OP_ERROR(dir, "chmod"); \
+       } \
+}
+
+static gint claws_mailmbox_create_tree(Folder *folder)
+{
+       gchar *rootpath;
+
+       g_return_val_if_fail(folder != NULL, -1);
+
+       CHDIR_RETURN_VAL_IF_FAIL(get_home_dir(), -1);
+       rootpath = LOCAL_FOLDER(folder)->rootpath;
+       MAKE_DIR_IF_NOT_EXIST(rootpath);
+       CHDIR_RETURN_VAL_IF_FAIL(rootpath, -1);
+
+       return 0;
+}
+
+#undef MAKE_DIR_IF_NOT_EXIST
+
+
+static char * quote_mailbox(char * mb)
+{
+        char path[PATH_MAX];
+        char * str;
+        size_t remaining;
+        char * p;
+        
+        remaining = sizeof(path) - 1;
+        p = path;
+
+        while (* mb != 0) {
+                
+                if (((* mb >= 'a') && (* mb <= 'z')) ||
+                    ((* mb >= 'A') && (* mb <= 'Z')) ||
+                    ((* mb >= '0') && (* mb <= '9'))) {
+                        if (remaining < 1)
+                                return NULL;
+                        * p = * mb;
+                        p ++;
+                        remaining --;
+                }
+                else {
+                        if (remaining < 3)
+                                return NULL;
+                        * p = '%';
+                        p ++;
+                        snprintf(p, 3, "%02x", (unsigned char) (* mb));
+                        p += 2;
+                }
+                mb ++;
+        }
+        
+        * p = 0;
+
+        str = strdup(path);
+        if (str == NULL)
+                return NULL;
+        
+        return str;
+}
+
+static gchar *claws_mailmbox_item_get_path(Folder *folder, FolderItem *item)
+{
+       gchar *itempath, *path;
+        gchar * folderpath;
+
+        if (item->path == NULL)
+                return NULL;
+
+        if (folder->name == NULL)
+                return NULL;
+
+        folderpath = quote_mailbox(folder->name);
+        if (folderpath == NULL)         
+               return NULL;
+        itempath = quote_mailbox(item->path);
+        if (itempath == NULL) {
+                free(folderpath);
+               return NULL;
+        }
+        path = g_strconcat(get_cache_dir(),
+            G_DIR_SEPARATOR_S, folderpath,
+            G_DIR_SEPARATOR_S, itempath, NULL);         
+        free(itempath);
+        free(folderpath);
+        
+       return path;
+}
diff --git a/src/plugins/mailmbox/mailmbox_folder.h b/src/plugins/mailmbox/mailmbox_folder.h
new file mode 100644 (file)
index 0000000..83e973d
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2003 Hiroyuki Yamamoto
+ *
+ * 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 3 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 __MAILMBOX_FOLDER_H__
+#define __MAILMBOX_FOLDER_H__
+
+#include <glib.h>
+
+#include "folder.h"
+#include "localfolder.h"
+
+typedef struct _MAILMBOXFolder MAILMBOXFolder;
+
+#define MAILMBOX_FOLDER(obj)           ((MAILMBOXFolder *)obj)
+
+struct _MAILMBOXFolder
+{
+       LocalFolder lfolder;
+};
+
+FolderClass *claws_mailmbox_get_class  (void);
+
+#endif /* __MH_H__ */
diff --git a/src/plugins/mailmbox/mailmbox_gtk.deps b/src/plugins/mailmbox/mailmbox_gtk.deps
new file mode 100644 (file)
index 0000000..c0e7f78
--- /dev/null
@@ -0,0 +1 @@
+mailmbox
diff --git a/src/plugins/mailmbox/mailmbox_parse.c b/src/plugins/mailmbox/mailmbox_parse.c
new file mode 100644 (file)
index 0000000..ea1dd58
--- /dev/null
@@ -0,0 +1,624 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#include "mailmbox_parse.h"
+
+#include "mailmbox.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define UID_HEADER "X-LibEtPan-UID:"
+
+#include "utils.h"
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+enum {
+  UNSTRUCTURED_START,
+  UNSTRUCTURED_CR,
+  UNSTRUCTURED_LF,
+  UNSTRUCTURED_WSP,
+  UNSTRUCTURED_OUT
+};
+
+static inline int
+claws_mailmbox_fields_parse(char * str, size_t length,
+                     size_t * index,
+                     uint32_t * puid,
+                     size_t * phlen)
+{
+  size_t cur_token;
+  int r;
+  size_t hlen;
+  size_t uid;
+  int end;
+
+  cur_token = * index;
+
+  end = FALSE;
+  uid = 0;
+  while (!end) {
+    size_t begin;
+
+    begin = cur_token;
+
+    r = mailimf_ignore_field_parse(str, length, &cur_token);
+    switch (r) {
+    case MAILIMF_NO_ERROR:
+      if (str[begin] == 'X') {
+
+       if (strncasecmp(str + begin, UID_HEADER, strlen(UID_HEADER)) == 0) {
+         begin += strlen(UID_HEADER);
+
+         while (str[begin] == ' ')
+           begin ++;
+         
+         uid = strtoul(str + begin, NULL, 10);
+       }
+      }
+      
+      break;
+    case MAILIMF_ERROR_PARSE:
+    default:
+      end = TRUE;
+      break;
+    }
+  }
+
+  hlen = cur_token - * index;
+
+  * phlen = hlen;
+  * puid = uid;
+  * index = cur_token;
+
+  return MAILMBOX_NO_ERROR;
+}
+
+enum {
+  IN_MAIL,
+  FIRST_CR,
+  FIRST_LF,
+  SECOND_CR,
+  SECOND_LF,
+  PARSING_F,
+  PARSING_R,
+  PARSING_O,
+  PARSING_M,
+  OUT_MAIL
+};
+
+
+
+
+static inline int
+claws_mailmbox_single_parse(char * str, size_t length,
+                     size_t * index,
+                     size_t * pstart,
+                     size_t * pstart_len,
+                     size_t * pheaders,
+                     size_t * pheaders_len,
+                     size_t * pbody,
+                     size_t * pbody_len,
+                     size_t * psize,
+                     size_t * ppadding,
+                     uint32_t * puid)
+{
+  size_t cur_token;
+  size_t start;
+  size_t start_len;
+  size_t headers;
+  size_t headers_len;
+  size_t body;
+  size_t end;
+  size_t next;
+  size_t message_length;
+  uint32_t uid;
+  int r;
+#if 0
+  int in_mail_data;
+#endif
+#if 0
+  size_t begin;
+#endif
+
+  int state;
+
+  cur_token = * index;
+
+  if (cur_token >= length)
+    return MAILMBOX_ERROR_PARSE;
+
+  start = cur_token;
+  start_len = 0;
+  headers = cur_token;
+
+  if (cur_token + 5 < length) {
+    if (strncmp(str + cur_token, "From ", 5) == 0) {
+      cur_token += 5;
+      while (str[cur_token] != '\n') {
+        cur_token ++;
+        if (cur_token >= length)
+          break;
+      }
+      if (cur_token < length) {
+        cur_token ++;
+        headers = cur_token;
+        start_len = headers - start;
+      }
+    }
+  }
+
+  next = length;
+
+  r = claws_mailmbox_fields_parse(str, length, &cur_token,
+                           &uid, &headers_len);
+  if (r != MAILMBOX_NO_ERROR)
+    return r;
+
+  /* save position */
+#if 0
+  begin = cur_token;
+#endif
+  
+  mailimf_crlf_parse(str, length, &cur_token);
+
+#if 0
+  if (str[cur_token] == 'F') {
+    printf("start !\n");
+    printf("%50.50s\n", str + cur_token);
+    getchar();
+  }
+#endif
+  
+  body = cur_token;
+
+  /* restore position */
+  /*  cur_token = begin; */
+
+  state = FIRST_LF;
+
+  end = length;
+
+#if 0
+  in_mail_data = 0;
+#endif
+  while (state != OUT_MAIL) {
+
+    if (cur_token >= length) {
+      if (state == IN_MAIL)
+       end = length;
+      next = length;
+      break;
+    }
+
+    switch(state) {
+    case IN_MAIL:
+      switch(str[cur_token]) {
+      case '\r':
+        state = FIRST_CR;
+        break;
+      case '\n':
+        state = FIRST_LF;
+        break;
+      case 'F':
+        if (cur_token == body) {
+          end = cur_token;
+          next = cur_token;
+          state = PARSING_F;
+        }
+        break;
+#if 0
+      default:
+        in_mail_data = 1;
+        break;
+#endif
+      }
+      break;
+      
+    case FIRST_CR:
+      end = cur_token;
+      switch(str[cur_token]) {
+      case '\r':
+        state = SECOND_CR;
+        break;
+      case '\n':
+        state = FIRST_LF;
+        break;
+      default:
+        state = IN_MAIL;
+#if 0
+        in_mail_data = 1;
+#endif
+        break;
+      }
+      break;
+      
+    case FIRST_LF:
+      end = cur_token;
+      switch(str[cur_token]) {
+      case '\r':
+        state = SECOND_CR;
+        break;
+      case '\n':
+        state = SECOND_LF;
+        break;
+      default:
+        state = IN_MAIL;
+#if 0
+        in_mail_data = 1;
+#endif
+        break;
+      }
+      break;
+      
+    case SECOND_CR:
+      switch(str[cur_token]) {
+        case '\r':
+          end = cur_token;
+          break;
+        case '\n':
+          state = SECOND_LF;
+          break;
+        case 'F':
+          next = cur_token;
+          state = PARSING_F;
+          break;
+        default:
+          state = IN_MAIL;
+#if 0
+          in_mail_data = 1;
+#endif
+          break;
+      }
+      break;
+
+    case SECOND_LF:
+      switch(str[cur_token]) {
+        case '\r':
+          state = SECOND_CR;
+          break;
+        case '\n':
+          end = cur_token;
+          break;
+        case 'F':
+          next = cur_token;
+          state = PARSING_F;
+          break;
+        default:
+          state = IN_MAIL;
+#if 0
+          in_mail_data = 1;
+#endif
+          break;
+      }
+      break;
+      
+    case PARSING_F:
+      switch(str[cur_token]) {
+        case 'r':
+          state = PARSING_R;
+          break;
+        default:
+          state = IN_MAIL;
+#if 0
+          in_mail_data = 1;
+#endif
+          break;
+      }
+      break;
+      
+    case PARSING_R:
+      switch(str[cur_token]) {
+        case 'o':
+          state = PARSING_O;
+          break;
+        default:
+          state = IN_MAIL;
+#if 0
+          in_mail_data = 1;
+#endif
+          break;
+      }
+      break;
+      
+    case PARSING_O:
+      switch(str[cur_token]) {
+        case 'm':
+          state = PARSING_M;
+          break;
+        default:
+          state = IN_MAIL;
+#if 0
+          in_mail_data = 1;
+#endif
+          break;
+      }
+      break;
+
+    case PARSING_M:
+      switch(str[cur_token]) {
+        case ' ':
+          state = OUT_MAIL;
+          break;
+      default:
+          state = IN_MAIL;
+          break;
+      }
+      break;
+    }
+    
+    cur_token ++;
+  }
+
+  message_length = end - start;
+
+  * pstart = start;
+  * pstart_len = start_len;
+  * pheaders = headers;
+  * pheaders_len = headers_len;
+  * pbody = body;
+  * pbody_len = end - body;
+  * psize = message_length;
+  * ppadding = next - end;
+  * puid = uid;
+
+  * index = next;
+
+  return MAILMBOX_NO_ERROR;
+}
+
+
+int
+claws_mailmbox_parse_additionnal(struct claws_mailmbox_folder * folder,
+                          size_t * index)
+{
+  size_t cur_token;
+
+  size_t start;
+  size_t start_len;
+  size_t headers;
+  size_t headers_len;
+  size_t body;
+  size_t body_len;
+  size_t size;
+  size_t padding;
+  uint32_t uid;
+  int r;
+  int res;
+
+  uint32_t max_uid;
+  uint32_t first_index;
+  unsigned int i;
+  unsigned int j;
+
+  cur_token = * index;
+
+  /* remove temporary UID that we will parse */
+
+  first_index = carray_count(folder->mb_tab);
+
+  for(i = 0 ; i < carray_count(folder->mb_tab) ; i++) {
+    struct claws_mailmbox_msg_info * info;
+    
+    info = carray_get(folder->mb_tab, i);
+
+    if (info->msg_start < cur_token) {
+      continue;
+    }
+
+    if (!info->msg_written_uid) {
+      chashdatum key;
+      
+      key.data = &info->msg_uid;
+      key.len = sizeof(info->msg_uid);
+      
+      chash_delete(folder->mb_hash, &key, NULL);
+      carray_delete_fast(folder->mb_tab, i);
+      claws_mailmbox_msg_info_free(info);
+      if (i < first_index)
+       first_index = i;
+    }
+  }
+
+  /* make a sequence in the table */
+
+  max_uid = folder->mb_written_uid;
+
+  i = 0;
+  j = 0;
+  while (i < carray_count(folder->mb_tab)) {
+    struct claws_mailmbox_msg_info * info;
+    
+    info = carray_get(folder->mb_tab, i);
+    if (info != NULL) {
+      carray_set(folder->mb_tab, j, info);
+
+      if (info->msg_uid > max_uid)
+       max_uid = info->msg_uid;
+
+      info->msg_index = j;
+      j ++;
+    }
+    i ++;
+  }
+  carray_set_size(folder->mb_tab, j);
+
+  /* parse content */
+
+  first_index = j;
+
+  while (1) {
+    struct claws_mailmbox_msg_info * info;
+    chashdatum key;
+    chashdatum data;
+    
+    r = claws_mailmbox_single_parse(folder->mb_mapping, folder->mb_mapping_size,
+                             &cur_token,
+                             &start, &start_len,
+                             &headers, &headers_len,
+                             &body, &body_len,
+                             &size, &padding, &uid);
+    if (r == MAILMBOX_NO_ERROR) {
+      /* do nothing */
+    }
+    else if (r == MAILMBOX_ERROR_PARSE) {
+      break;
+    } else {
+      res = r;
+      goto err;
+    }
+    
+    key.data = &uid;
+    key.len = sizeof(uid);
+    
+    r = chash_get(folder->mb_hash, &key, &data);
+    if (r == 0) {
+      info = data.data;
+      
+      if (!info->msg_written_uid) {
+       /* some new mail has been written and override an
+          existing temporary UID */
+        
+       chash_delete(folder->mb_hash, &key, NULL);
+       info->msg_uid = 0;
+
+       if (info->msg_index < first_index)
+         first_index = info->msg_index;
+      }
+      else
+        uid = 0;
+    }
+
+    if (uid > max_uid)
+      max_uid = uid;
+
+    r = claws_mailmbox_msg_info_update(folder,
+                                start, start_len, headers, headers_len,
+                                body, body_len, size, padding, uid);
+    if (r != MAILMBOX_NO_ERROR) {
+      debug_print("claws_mailmbox_msg_info_update failed with %d\n", r);
+      res = r;
+      goto err;
+    }
+  }
+
+  * index = cur_token;
+
+  folder->mb_written_uid = max_uid;
+
+  /* attribute uid */
+
+  for(i = first_index ; i < carray_count(folder->mb_tab) ; i ++) {
+    struct claws_mailmbox_msg_info * info;
+    chashdatum key;
+    chashdatum data;
+
+    info = carray_get(folder->mb_tab, i);
+
+    if (info->msg_uid != 0) {
+      continue;
+    }
+
+    max_uid ++;
+    info->msg_uid = max_uid;
+    
+    key.data = &info->msg_uid;
+    key.len = sizeof(info->msg_uid);
+    data.data = info;
+    data.len = 0;
+    
+    r = chash_set(folder->mb_hash, &key, &data, NULL);
+    if (r < 0) {
+      debug_print("chash_set failed with %d\n", r);
+      res = MAILMBOX_ERROR_MEMORY;
+      goto err;
+    }
+  }
+
+  folder->mb_max_uid = max_uid;
+
+  return MAILMBOX_NO_ERROR;
+
+ err:
+  return res;
+}
+
+static void flush_uid(struct claws_mailmbox_folder * folder)
+{
+  unsigned int i;
+  
+  for(i = 0 ; i < carray_count(folder->mb_tab) ; i++) {
+    struct claws_mailmbox_msg_info * info;
+    
+    info = carray_get(folder->mb_tab, i);
+    if (info != NULL)
+      claws_mailmbox_msg_info_free(info);
+  }
+  
+  chash_clear(folder->mb_hash);
+  carray_set_size(folder->mb_tab, 0);
+}
+
+int claws_mailmbox_parse(struct claws_mailmbox_folder * folder)
+{
+  int r;
+  int res;
+  size_t cur_token;
+
+  flush_uid(folder);
+  
+  cur_token = 0;
+
+  r = claws_mailmbox_parse_additionnal(folder, &cur_token);
+
+  if (r != MAILMBOX_NO_ERROR) {
+    res = r;
+    goto err;
+  }
+
+  return MAILMBOX_NO_ERROR;
+
+ err:
+  return res;
+}
diff --git a/src/plugins/mailmbox/mailmbox_parse.h b/src/plugins/mailmbox/mailmbox_parse.h
new file mode 100644 (file)
index 0000000..8ab0d9f
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef MAILMBOX_PARSE_H
+
+#define MAILMBOX_PARSE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "mailmbox_types.h"
+
+int claws_mailmbox_parse(struct claws_mailmbox_folder * folder);
+
+int
+claws_mailmbox_parse_additionnal(struct claws_mailmbox_folder * folder,
+                          size_t * index);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/plugins/mailmbox/mailmbox_types.c b/src/plugins/mailmbox/mailmbox_types.c
new file mode 100644 (file)
index 0000000..2bddfaf
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#include "mailmbox_types.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+/* *********************************************************************** */
+
+int claws_mailmbox_msg_info_update(struct claws_mailmbox_folder * folder,
+                            size_t msg_start, size_t msg_start_len,
+                            size_t msg_headers, size_t msg_headers_len,
+                            size_t msg_body, size_t msg_body_len,
+                            size_t msg_size, size_t msg_padding,
+                            uint32_t msg_uid)
+{
+  struct claws_mailmbox_msg_info * info;
+  int res;
+  chashdatum key;
+  chashdatum data;
+  int r;
+  
+  key.data = &msg_uid;
+  key.len = sizeof(msg_uid);
+  r = chash_get(folder->mb_hash, &key, &data);
+  if (r < 0) {
+    unsigned int index;
+
+    info = claws_mailmbox_msg_info_new(msg_start, msg_start_len,
+        msg_headers, msg_headers_len,
+        msg_body, msg_body_len, msg_size, msg_padding, msg_uid);
+    if (info == NULL) {
+      res = MAILMBOX_ERROR_MEMORY;
+      goto err;
+    }
+
+    r = carray_add(folder->mb_tab, info, &index);
+    if (r < 0) {
+      claws_mailmbox_msg_info_free(info);
+      res = MAILMBOX_ERROR_MEMORY;
+      goto err;
+    }
+
+    if (msg_uid != 0) {
+      chashdatum key;
+      chashdatum data;
+      
+      key.data = &msg_uid;
+      key.len = sizeof(msg_uid);
+      data.data = info;
+      data.len = 0;
+      
+      r = chash_set(folder->mb_hash, &key, &data, NULL);
+      if (r < 0) {
+       claws_mailmbox_msg_info_free(info);
+       carray_delete(folder->mb_tab, index);
+       res = MAILMBOX_ERROR_MEMORY;
+       goto err;
+      }
+    }
+    
+    info->msg_index = index;
+  }
+  else {
+    info = data.data;
+    
+    info->msg_start = msg_start;
+    info->msg_start_len = msg_start_len;
+    info->msg_headers = msg_headers;
+    info->msg_headers_len = msg_headers_len;
+    info->msg_body = msg_body;
+    info->msg_body_len = msg_body_len;
+    info->msg_size = msg_size;
+    info->msg_padding = msg_padding;
+  }
+
+  return MAILMBOX_NO_ERROR;
+
+ err:
+  return res;
+}
+
+
+struct claws_mailmbox_msg_info *
+claws_mailmbox_msg_info_new(size_t msg_start, size_t msg_start_len,
+                     size_t msg_headers, size_t msg_headers_len,
+                     size_t msg_body, size_t msg_body_len,
+                     size_t msg_size, size_t msg_padding,
+                     uint32_t msg_uid)
+{
+  struct claws_mailmbox_msg_info * info;
+
+  info = malloc(sizeof(* info));
+  if (info == NULL)
+    return NULL;
+
+  info->msg_index = 0;
+  info->msg_uid = msg_uid;
+  if (msg_uid != 0)
+    info->msg_written_uid = TRUE;
+  else
+    info->msg_written_uid = FALSE;
+  info->msg_deleted = FALSE;
+
+  info->msg_start = msg_start;
+  info->msg_start_len = msg_start_len;
+
+  info->msg_headers = msg_headers;
+  info->msg_headers_len = msg_headers_len;
+
+  info->msg_body = msg_body;
+  info->msg_body_len = msg_body_len;
+
+  info->msg_size = msg_size;
+
+  info->msg_padding = msg_padding;
+
+  return info;
+}
+
+void claws_mailmbox_msg_info_free(struct claws_mailmbox_msg_info * info)
+{
+  free(info);
+}
+
+
+/* append info */
+
+struct claws_mailmbox_append_info *
+claws_mailmbox_append_info_new(const char * ai_message, size_t ai_size)
+{
+  struct claws_mailmbox_append_info * info;
+
+  info = malloc(sizeof(* info));
+  if (info == NULL)
+    return NULL;
+
+  info->ai_message = ai_message;
+  info->ai_size = ai_size;
+
+  return info;
+}
+
+void claws_mailmbox_append_info_free(struct claws_mailmbox_append_info * info)
+{
+  free(info);
+}
+
+struct claws_mailmbox_folder * claws_mailmbox_folder_new(const char * mb_filename)
+{
+  struct claws_mailmbox_folder * folder;
+
+  folder = malloc(sizeof(* folder));
+  if (folder == NULL)
+    goto err;
+
+  strncpy(folder->mb_filename, mb_filename, PATH_MAX);
+
+  folder->mb_mtime = (time_t) -1;
+
+  folder->mb_fd = -1;
+  folder->mb_read_only = TRUE;
+  folder->mb_no_uid = TRUE;
+
+  folder->mb_changed = FALSE;
+  folder->mb_deleted_count = 0;
+  
+  folder->mb_mapping = NULL;
+  folder->mb_mapping_size = 0;
+
+  folder->mb_written_uid = 0;
+  folder->mb_max_uid = 0;
+
+  folder->mb_hash = chash_new(CHASH_DEFAULTSIZE, CHASH_COPYKEY);
+  if (folder->mb_hash == NULL)
+    goto free;
+  
+  folder->mb_tab = carray_new(128);
+  if (folder->mb_tab == NULL)
+    goto free_hash;
+
+  return folder;
+
+ free_hash:
+  chash_free(folder->mb_hash);
+ free:
+  free(folder);
+ err:
+  return NULL;
+}
+
+void claws_mailmbox_folder_free(struct claws_mailmbox_folder * folder)
+{
+  unsigned int i;
+
+  for(i = 0 ; i < carray_count(folder->mb_tab) ; i++) {
+    struct claws_mailmbox_msg_info * info;
+
+    info = carray_get(folder->mb_tab, i);
+    if (info != NULL)
+      claws_mailmbox_msg_info_free(info);
+  }
+
+  carray_free(folder->mb_tab);
+  
+  chash_free(folder->mb_hash);
+
+  free(folder);
+}
diff --git a/src/plugins/mailmbox/mailmbox_types.h b/src/plugins/mailmbox/mailmbox_types.h
new file mode 100644 (file)
index 0000000..a207809
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef MAILMBOX_TYPES_H
+
+#define MAILMBOX_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+#include <limits.h>
+
+#include "mailimf.h"
+#include "carray.h"
+#include "chash.h"
+
+enum {
+  MAILMBOX_NO_ERROR = 0,
+  MAILMBOX_ERROR_PARSE,
+  MAILMBOX_ERROR_INVAL,
+  MAILMBOX_ERROR_FILE_NOT_FOUND,
+  MAILMBOX_ERROR_MEMORY,
+  MAILMBOX_ERROR_TEMPORARY_FILE,
+  MAILMBOX_ERROR_FILE,
+  MAILMBOX_ERROR_MSG_NOT_FOUND,
+  MAILMBOX_ERROR_READONLY,
+};
+
+
+struct claws_mailmbox_folder {
+  char mb_filename[PATH_MAX];
+
+  time_t mb_mtime;
+
+  int mb_fd;
+  int mb_read_only;
+  int mb_no_uid;
+
+  int mb_changed;
+  unsigned int mb_deleted_count;
+  
+  char * mb_mapping;
+  size_t mb_mapping_size;
+
+  uint32_t mb_written_uid;
+  uint32_t mb_max_uid;
+
+  chash * mb_hash;
+  carray * mb_tab;
+};
+
+struct claws_mailmbox_folder * claws_mailmbox_folder_new(const char * mb_filename);
+void claws_mailmbox_folder_free(struct claws_mailmbox_folder * folder);
+
+
+struct claws_mailmbox_msg_info {
+  unsigned int msg_index;
+  uint32_t msg_uid;
+  int msg_written_uid;
+  int msg_deleted;
+
+  size_t msg_start;
+  size_t msg_start_len;
+
+  size_t msg_headers;
+  size_t msg_headers_len;
+
+  size_t msg_body;
+  size_t msg_body_len;
+
+  size_t msg_size;
+
+  size_t msg_padding;
+};
+
+
+int claws_mailmbox_msg_info_update(struct claws_mailmbox_folder * folder,
+                            size_t msg_start, size_t msg_start_len,
+                            size_t msg_headers, size_t msg_headers_len,
+                            size_t msg_body, size_t msg_body_len,
+                            size_t msg_size, size_t msg_padding,
+                            uint32_t msg_uid);
+
+struct claws_mailmbox_msg_info *
+claws_mailmbox_msg_info_new(size_t msg_start, size_t msg_start_len,
+                     size_t msg_headers, size_t msg_headers_len,
+                     size_t msg_body, size_t msg_body_len,
+                     size_t msg_size, size_t msg_padding,
+                     uint32_t msg_uid);
+
+void claws_mailmbox_msg_info_free(struct claws_mailmbox_msg_info * info);
+
+struct claws_mailmbox_append_info {
+  const char * ai_message;
+  size_t ai_size;
+};
+
+struct claws_mailmbox_append_info *
+claws_mailmbox_append_info_new(const char * ai_message, size_t ai_size);
+
+void claws_mailmbox_append_info_free(struct claws_mailmbox_append_info * info);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/plugins/mailmbox/mmapstring.c b/src/plugins/mailmbox/mmapstring.c
new file mode 100644 (file)
index 0000000..9737fee
--- /dev/null
@@ -0,0 +1,525 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#include "mmapstring.h"
+
+#include "chash.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <string.h>
+#include <pthread.h>
+#include <limits.h>
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+#define MMAP_STRING_DEFAULT_CEIL (8 * 1024 * 1024)
+
+#define DEFAULT_TMP_PATH "/tmp"
+
+static char tmpdir[PATH_MAX] = DEFAULT_TMP_PATH;
+
+static size_t mmap_string_ceil = MMAP_STRING_DEFAULT_CEIL;
+
+/* MMAPString references */
+
+static pthread_mutex_t mmapstring_lock = PTHREAD_MUTEX_INITIALIZER;
+static chash * mmapstring_hashtable = NULL;
+
+static void mmapstring_hashtable_init()
+{
+  mmapstring_hashtable = chash_new(CHASH_DEFAULTSIZE, CHASH_COPYKEY);
+}
+
+void mmap_string_set_tmpdir(char * directory)
+{
+  strncpy(tmpdir, directory, PATH_MAX);
+  tmpdir[PATH_MAX - 1] = 0;
+}
+
+
+int mmap_string_ref(MMAPString * string)
+{
+  chash * ht;
+  int r;
+  chashdatum key;
+  chashdatum data;
+  
+  pthread_mutex_lock(&mmapstring_lock);
+  if (mmapstring_hashtable == NULL) {
+    mmapstring_hashtable_init();
+  }
+  ht = mmapstring_hashtable;
+  
+  if (ht == NULL) {
+    pthread_mutex_unlock(&mmapstring_lock);
+    return -1;
+  }
+  
+  key.data = &string->str;
+  key.len = sizeof(string->str);
+  data.data = string;
+  data.len = 0;
+  
+  r = chash_set(mmapstring_hashtable, &key, &data, NULL);
+  pthread_mutex_unlock(&mmapstring_lock);
+  
+  if (r < 0)
+    return r;
+
+  return 0;
+}
+
+int mmap_string_unref(char * str)
+{
+  MMAPString * string;
+  chash * ht;
+  chashdatum key;
+  chashdatum data;
+  int r;
+
+  pthread_mutex_lock(&mmapstring_lock);
+  ht = mmapstring_hashtable;
+  
+  if (ht == NULL) {
+    pthread_mutex_unlock(&mmapstring_lock);
+    return -1;
+  }
+  
+  key.data = &str;
+  key.len = sizeof(str);
+
+  r = chash_get(ht, &key, &data);
+  if (r < 0)
+    string = NULL;
+  else
+    string = data.data;
+  
+  if (string != NULL) {
+    chash_delete(ht, &key, NULL);
+    if (chash_count(ht) == 0) {
+      chash_free(ht);
+      mmapstring_hashtable = NULL;
+    }
+  }
+  
+  pthread_mutex_unlock(&mmapstring_lock);
+
+  if (string != NULL) {
+    mmap_string_free(string);
+    return 0;
+  }
+  else
+    return -1;
+}
+
+
+
+/* MMAPString */
+
+#define MY_MAXSIZE ((size_t) -1)
+
+static inline size_t
+nearest_power (size_t base, size_t num)    
+{
+  if (num > MY_MAXSIZE / 2) {
+    return MY_MAXSIZE;
+  }
+  else {
+    size_t n = base;
+    
+    while (n < num)
+      n <<= 1;
+    
+    return n;
+  }
+}
+
+void mmap_string_set_ceil(size_t ceil)
+{
+  mmap_string_ceil = ceil;
+}
+
+/* Strings.
+ */
+
+static MMAPString * mmap_string_realloc_file(MMAPString * string)
+{
+  char * data;
+
+  if (string->fd == -1) {
+    char tmpfilename[PATH_MAX];
+    int fd;
+
+    * tmpfilename = 0;
+    strcat(tmpfilename, tmpdir);
+    strcat(tmpfilename, "/libetpan-mmapstring-XXXXXX");
+
+    fd = mkstemp(tmpfilename);
+    if (fd == -1)
+      return NULL;
+
+    if (unlink(tmpfilename) == -1) {
+      close(fd);
+      return NULL;
+    }
+
+    if (ftruncate(fd, string->allocated_len) == -1) {
+      close(fd);
+      return NULL;
+    }
+
+    data = mmap(NULL, string->allocated_len, PROT_WRITE | PROT_READ,
+               MAP_SHARED, fd, 0);
+
+    if (data == MAP_FAILED) {
+      close(fd);
+      return NULL;
+    }
+
+    if (string->str != NULL)
+      memcpy(data, string->str, string->len + 1);
+
+    string->fd = fd;
+    string->mmapped_size = string->allocated_len;
+    free(string->str);
+    string->str = data;
+  }
+  else {
+    if (munmap(string->str, string->mmapped_size) == -1)
+      return NULL;
+
+    if (ftruncate(string->fd, string->allocated_len) == -1)
+      return NULL;
+    
+    data = mmap(NULL, string->allocated_len, PROT_WRITE | PROT_READ,
+               MAP_SHARED, string->fd, 0);
+
+    if (data == MAP_FAILED)
+      return NULL;
+
+    string->mmapped_size = string->allocated_len;
+    string->str = data;
+  }
+  
+  return string;
+}
+
+static MMAPString * mmap_string_realloc_memory(MMAPString * string)
+{
+  char * tmp;
+
+  tmp =  realloc (string->str, string->allocated_len);
+  
+  if (tmp == NULL)
+    string = NULL;
+  else
+    string->str = tmp;
+
+  return string;
+}
+
+static MMAPString *
+mmap_string_maybe_expand (MMAPString* string,
+                         size_t len) 
+{
+  if (string->len + len >= string->allocated_len)
+    {
+      size_t old_size;
+      MMAPString * newstring;
+
+      old_size = string->allocated_len;
+
+      string->allocated_len = nearest_power (1, string->len + len + 1);
+      
+#ifndef MMAP_UNAVAILABLE
+      if (string->allocated_len > mmap_string_ceil)
+       newstring = mmap_string_realloc_file(string);
+      else {
+#endif
+       newstring = mmap_string_realloc_memory(string);
+#ifndef MMAP_UNAVAILABLE
+       if (newstring == NULL)
+         newstring = mmap_string_realloc_file(string);
+      }
+#endif
+
+      if (newstring == NULL)
+       string->allocated_len = old_size;
+    }
+
+  return string;
+}
+
+MMAPString*
+mmap_string_sized_new (size_t dfl_size)
+{
+  MMAPString *string;
+  string = malloc(sizeof(* string));
+  if (string == NULL)
+    return NULL;
+
+  string->allocated_len = 0;
+  string->len   = 0;
+  string->str   = NULL;
+  string->fd    = -1;
+  string->mmapped_size = 0;
+
+  if (mmap_string_maybe_expand (string, MAX (dfl_size, 2)) == NULL)
+    return NULL;
+
+  string->str[0] = 0;
+
+  return string;
+}
+
+MMAPString*
+mmap_string_new (const char *init)
+{
+  MMAPString *string;
+
+  string = mmap_string_sized_new (init ? strlen (init) + 2 : 2);
+  if (string == NULL)
+    return NULL;
+
+  if (init)
+    mmap_string_append (string, init);
+
+  return string;
+}
+
+MMAPString*
+mmap_string_new_len (const char *init,
+                    size_t len)    
+{
+  MMAPString *string;
+
+  if (len <= 0)
+    return mmap_string_new (init);
+  else
+    {
+      string = mmap_string_sized_new (len);
+      
+      if (init)
+        mmap_string_append_len (string, init, len);
+      
+      return string;
+    }
+}
+
+void
+mmap_string_free (MMAPString *string)
+{
+  if (string == NULL)
+    return;
+
+  if (string->fd != -1) {
+    munmap(string->str, string->mmapped_size);
+    close(string->fd);
+  }
+  else {
+    free (string->str);
+  }
+  free(string);
+}
+
+MMAPString*
+mmap_string_assign (MMAPString     *string,
+                   const char *rval)
+{
+  mmap_string_truncate (string, 0);
+  if (mmap_string_append (string, rval) == NULL)
+    return NULL;
+
+  return string;
+}
+
+MMAPString*
+mmap_string_truncate (MMAPString *string,
+                     size_t    len)    
+{
+  string->len = MIN (len, string->len);
+  string->str[string->len] = 0;
+
+  return string;
+}
+
+/**
+ * mmap_string_set_size:
+ * @string: a #MMAPString
+ * @len: the new length
+ * 
+ * Sets the length of a #MMAPString. If the length is less than
+ * the current length, the string will be truncated. If the
+ * length is greater than the current length, the contents
+ * of the newly added area are undefined. (However, as
+ * always, string->str[string->len] will be a nul byte.) 
+ * 
+ * Return value: @string
+ **/
+MMAPString*
+mmap_string_set_size (MMAPString *string,
+                     size_t    len)    
+{
+  if (len >= string->allocated_len)
+    if (mmap_string_maybe_expand (string, len - string->len) == NULL)
+      return NULL;
+  
+  string->len = len;
+  string->str[len] = 0;
+
+  return string;
+}
+
+/*
+static int in_mapped_zone(MMAPString * string, char * val)
+{
+  return (val >= string->str) && (val < string->str + string->mmapped_size);
+}
+*/
+
+MMAPString*
+mmap_string_insert_len (MMAPString     *string,
+                       size_t       pos,    
+                       const char *val,
+                       size_t       len)    
+{
+  if (mmap_string_maybe_expand (string, len) == NULL)
+    return NULL;
+    
+  if (pos < string->len)
+    memmove (string->str + pos + len, string->str + pos, string->len - pos);
+
+  /* insert the new string */
+  memmove (string->str + pos, val, len);
+
+  string->len += len;
+
+  string->str[string->len] = 0;
+
+  return string;
+}
+
+MMAPString*
+mmap_string_append (MMAPString     *string,
+                   const char *val)
+{  
+  return mmap_string_insert_len (string, string->len, val, strlen(val));
+}
+
+MMAPString*
+mmap_string_append_len (MMAPString      *string,
+                       const char *val,
+                       size_t       len)    
+{
+  return mmap_string_insert_len (string, string->len, val, len);
+}
+
+MMAPString*
+mmap_string_append_c (MMAPString *string,
+                     char    c)
+{
+  return mmap_string_insert_c (string, string->len, c);
+}
+
+MMAPString*
+mmap_string_prepend (MMAPString     *string,
+                    const char *val)
+{
+  return mmap_string_insert_len (string, 0, val, strlen(val));
+}
+
+MMAPString*
+mmap_string_prepend_len (MMAPString      *string,
+                        const char *val,
+                        size_t       len)    
+{
+  return mmap_string_insert_len (string, 0, val, len);
+}
+
+MMAPString*
+mmap_string_prepend_c (MMAPString *string,
+                      char    c)
+{  
+  return mmap_string_insert_c (string, 0, c);
+}
+
+MMAPString*
+mmap_string_insert (MMAPString     *string,
+                   size_t       pos,    
+                   const char *val)
+{
+  return mmap_string_insert_len (string, pos, val, strlen(val));
+}
+
+MMAPString*
+mmap_string_insert_c (MMAPString *string,
+                     size_t   pos,    
+                     char    c)
+{
+  if (mmap_string_maybe_expand (string, 1) == NULL)
+    return NULL;
+  
+  /* If not just an append, move the old stuff */
+  if (pos < string->len)
+    memmove (string->str + pos + 1, string->str + pos, string->len - pos);
+
+  string->str[pos] = c;
+
+  string->len += 1;
+
+  string->str[string->len] = 0;
+
+  return string;
+}
+
+MMAPString*
+mmap_string_erase (MMAPString *string,
+                  size_t    pos,    
+                  size_t    len)    
+{
+  if ((pos + len) < string->len)
+    memmove (string->str + pos, string->str + pos + len,
+            string->len - (pos + len));
+  
+  string->len -= len;
+  
+  string->str[string->len] = 0;
+
+  return string;
+}
diff --git a/src/plugins/mailmbox/mmapstring.h b/src/plugins/mailmbox/mmapstring.h
new file mode 100644 (file)
index 0000000..6d7227d
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * libEtPan! -- a mail stuff library
+ *
+ * Copyright (C) 2001, 2002 - DINH Viet Hoa
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the libEtPan! project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * $Id$
+ */
+
+#ifndef __MMAP_STRING_H__
+
+#define __MMAP_STRING_H__
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+#define TMPDIR "/tmp"
+*/
+
+typedef struct _MMAPString MMAPString;
+
+struct _MMAPString
+{
+  char * str;
+  size_t len;    
+  size_t allocated_len;
+  int fd;
+  size_t mmapped_size;
+  /*
+  char * old_non_mmapped_str;
+  */
+};
+
+/* configure location of mmaped files */
+
+void mmap_string_set_tmpdir(char * directory);
+
+/* Strings
+ */
+
+MMAPString * mmap_string_new (const char * init);
+
+MMAPString * mmap_string_new_len (const char * init,
+                                 size_t len);   
+
+MMAPString * mmap_string_sized_new (size_t dfl_size);
+
+void mmap_string_free (MMAPString * string);
+
+MMAPString * mmap_string_assign (MMAPString * string,
+                                const char * rval);
+
+MMAPString * mmap_string_truncate (MMAPString *string,
+                                  size_t len);    
+
+MMAPString * mmap_string_set_size (MMAPString * string,
+                                  size_t len);
+
+MMAPString * mmap_string_insert_len (MMAPString * string,
+                                    size_t pos,   
+                                    const char * val,
+                                    size_t len);  
+
+MMAPString * mmap_string_append (MMAPString * string,
+                                const char * val);
+
+MMAPString * mmap_string_append_len (MMAPString * string,
+                                    const char * val,
+                                    size_t len);  
+
+MMAPString * mmap_string_append_c (MMAPString * string,
+                                  char c);
+
+MMAPString * mmap_string_prepend (MMAPString * string,
+                                 const char * val);
+
+MMAPString * mmap_string_prepend_c (MMAPString * string,
+                                   char c);
+
+MMAPString * mmap_string_prepend_len (MMAPString * string,
+                                     const char * val,
+                                     size_t len);  
+
+MMAPString * mmap_string_insert (MMAPString * string,
+                                size_t pos,
+                                const char * val);
+
+MMAPString * mmap_string_insert_c (MMAPString *string,
+                                  size_t pos,
+                                  char c);
+
+MMAPString * mmap_string_erase(MMAPString * string,
+                              size_t pos,    
+                              size_t len);   
+
+void mmap_string_set_ceil(size_t ceil);
+
+int mmap_string_ref(MMAPString * string);
+int mmap_string_unref(char * str);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* __MMAP_STRING_H__ */
diff --git a/src/plugins/mailmbox/placeholder.txt b/src/plugins/mailmbox/placeholder.txt
deleted file mode 100644 (file)
index 3b94f91..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Placeholder
diff --git a/src/plugins/mailmbox/plugin.c b/src/plugins/mailmbox/plugin.c
new file mode 100644 (file)
index 0000000..adb8412
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * mailmbox Plugin -- mbox support for Sylpheed
+ * Copyright (C) 2003 Christoph Hohmann
+ * Copyright (C) 2003-2005 Hoa v. Dinh, Alfons Hoogervorst
+ *
+ * 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 3 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.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#  include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include "plugin.h"
+#include "folder.h"
+#include "mailmbox_folder.h"
+#include "common/version.h"
+#include "plugin_gtk.h"
+#include "plugin.h"
+#include "main.h"
+
+gint plugin_init(gchar **error)
+{
+       if (!check_plugin_version(MAKE_NUMERIC_VERSION(3,8,1,46),
+                               VERSION_NUMERIC, "Mailmbox", error))
+               return -1;
+
+       folder_register_class(claws_mailmbox_get_class());
+       plugin_gtk_init(error);
+       return 0;
+}
+
+gboolean plugin_done(void)
+{
+       plugin_gtk_done();
+       if (!claws_is_exiting())
+               folder_unregister_class(claws_mailmbox_get_class());
+       return TRUE;
+}
+
+const gchar *plugin_name(void)
+{
+       return _("mailmbox folder (etPan!)");
+}
+
+const gchar *plugin_desc(void)
+{
+       return _("This is a plugin to handle mailboxes in mbox format.");
+}
+
+const gchar *plugin_type(void)
+{
+       return "GTK2";
+}
+
+const gchar *plugin_licence(void)
+{
+               return "GPL3+";
+}
+
+const gchar *plugin_version(void)
+{
+       return VERSION;
+}
+
+struct PluginFeature *plugin_provides(void)
+{
+       static struct PluginFeature features[] = 
+               { {PLUGIN_FOLDERCLASS, N_("MBOX")},
+                 {PLUGIN_NOTHING, NULL}};
+       return features;
+}
diff --git a/src/plugins/mailmbox/plugin_gtk.c b/src/plugins/mailmbox/plugin_gtk.c
new file mode 100644 (file)
index 0000000..e2f5b00
--- /dev/null
@@ -0,0 +1,455 @@
+/*
+ * mailmbox Plugin -- mbox support for Sylpheed
+ * Copyright (C) 2003-2005 Christoph Hohmann, 
+ *                        Hoa v. Dinh, 
+ *                        Alfons Hoogervorst
+ *
+ * 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 3 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.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#  include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <gtk/gtk.h>
+
+#include "plugin.h"
+#include "folder.h"
+#include "mailmbox_folder.h"
+#include "mainwindow.h"
+#include "folderview.h"
+#include "inputdialog.h"
+#include "foldersel.h"
+#include "alertpanel.h"
+#include "main.h"
+#include "menu.h"
+#include "account.h"
+#include "prefs_actions.h"
+#include "summaryview.h"
+#include "folder_item_prefs.h"
+
+static void add_mailbox(GtkAction *action, gpointer callback_data);
+static void new_folder_cb(GtkAction *action, gpointer data);
+static void delete_folder_cb(GtkAction *action, gpointer data);
+static void rename_folder_cb(GtkAction *action, gpointer data);
+static void move_folder_cb(GtkAction *action, gpointer data);
+static void copy_folder_cb(GtkAction *action, gpointer data);
+static void update_tree_cb(GtkAction *action, gpointer data);
+static void remove_mailbox_cb(GtkAction *action, gpointer data);
+
+static GtkActionEntry claws_mailmbox_popup_entries[] = 
+{
+       {"FolderViewPopup/CreateNewFolder",     NULL, N_("Create _new folder..."), NULL, NULL, G_CALLBACK(new_folder_cb) },
+       {"FolderViewPopup/RenameFolder",        NULL, N_("_Rename folder..."), NULL, NULL, G_CALLBACK(rename_folder_cb) },
+       {"FolderViewPopup/MoveFolder",          NULL, N_("M_ove folder..."), NULL, NULL, G_CALLBACK(move_folder_cb) },
+       {"FolderViewPopup/CopyFolder",          NULL, N_("Cop_y folder..."), NULL, NULL, G_CALLBACK(copy_folder_cb) },
+       {"FolderViewPopup/DeleteFolder",        NULL, N_("_Delete folder..."), NULL, NULL, G_CALLBACK(delete_folder_cb) },
+       {"FolderViewPopup/CheckNewMessages",    NULL, N_("_Check for new messages"), NULL, NULL, G_CALLBACK(update_tree_cb) }, /*0*/
+       {"FolderViewPopup/CheckNewFolders",     NULL, N_("C_heck for new folders"), NULL, NULL, G_CALLBACK(update_tree_cb) }, /*1*/
+       {"FolderViewPopup/RebuildTree",         NULL, N_("R_ebuild folder tree"), NULL, NULL, G_CALLBACK(update_tree_cb) }, /*2*/
+       {"FolderViewPopup/RemoveMailbox",       NULL, N_("Remove _mailbox..."), NULL, NULL, G_CALLBACK(remove_mailbox_cb) },
+};                     
+static void set_sensitivity(GtkUIManager *ui_manager, FolderItem *item);
+static void add_menuitems(GtkUIManager *ui_manager, FolderItem *item);
+
+static FolderViewPopup claws_mailmbox_popup =
+{
+       "mailmbox",
+       "<MailmboxFolder>",
+       claws_mailmbox_popup_entries,
+       G_N_ELEMENTS(claws_mailmbox_popup_entries),
+       NULL, 0,
+       NULL, 0, 0, NULL,
+       add_menuitems,
+       set_sensitivity
+};
+
+static GtkActionEntry mainwindow_add_mailbox[] = {{
+       "File/AddMailbox/Mbox",
+       NULL, N_("mbox (etPan!)..."), NULL, NULL, G_CALLBACK(add_mailbox)
+}};
+
+static guint main_menu_id = 0;
+
+gint plugin_gtk_init(gchar **error)
+{
+       MainWindow *mainwin = mainwindow_get_mainwindow();
+
+       folderview_register_popup(&claws_mailmbox_popup);
+
+       gtk_action_group_add_actions(mainwin->action_group, mainwindow_add_mailbox,
+                       1, (gpointer)mainwin);
+       MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/File/AddMailbox", "Mbox", 
+                         "File/AddMailbox/Mbox", GTK_UI_MANAGER_MENUITEM,
+                         main_menu_id)
+
+       return 0;
+}
+
+void plugin_gtk_done(void)
+{
+       MainWindow *mainwin = mainwindow_get_mainwindow();
+       
+       if (mainwin == NULL || claws_is_exiting())
+               return;
+
+       folderview_unregister_popup(&claws_mailmbox_popup);
+
+       MENUITEM_REMUI_MANAGER(mainwin->ui_manager,mainwin->action_group, "File/AddMailbox/RSSyl", main_menu_id);
+       main_menu_id = 0;
+}
+
+static void add_menuitems(GtkUIManager *ui_manager, FolderItem *item)
+{
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "CreateNewFolder", "FolderViewPopup/CreateNewFolder", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorMbox1", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RenameFolder", "FolderViewPopup/RenameFolder", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "MoveFolder", "FolderViewPopup/MoveFolder", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "CopyFolder", "FolderViewPopup/CopyFolder", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorMbox2", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "DeleteFolder", "FolderViewPopup/DeleteFolder", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorMbox3", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "CheckNewMessages", "FolderViewPopup/CheckNewMessages", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "CheckNewFolders", "FolderViewPopup/CheckNewFolders", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RebuildTree", "FolderViewPopup/RebuildTree", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorMbox4", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RemoveMailbox", "FolderViewPopup/RemoveMailbox", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorMbox5", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR)
+}
+
+static void set_sensitivity(GtkUIManager *ui_manager, FolderItem *item)
+{
+       gboolean folder_is_normal = 
+                       item != NULL &&
+                       item->stype == F_NORMAL &&
+                       !folder_has_parent_of_type(item, F_OUTBOX) &&
+                       !folder_has_parent_of_type(item, F_DRAFT) &&
+                       !folder_has_parent_of_type(item, F_QUEUE) &&
+                       !folder_has_parent_of_type(item, F_TRASH);
+#define SET_SENS(name, sens) \
+       cm_menu_set_sensitive_full(ui_manager, "Popup/"name, sens)
+
+       SET_SENS("FolderViewPopup/CreateNewFolder",   item->stype != F_INBOX);
+       SET_SENS("FolderViewPopup/RenameFolder",       item->stype == F_NORMAL && folder_item_parent(item) != NULL);
+       SET_SENS("FolderViewPopup/MoveFolder",      folder_is_normal && folder_item_parent(item) != NULL);
+       SET_SENS("FolderViewPopup/DeleteFolder",            item->stype == F_NORMAL && folder_item_parent(item) != NULL);
+
+       SET_SENS("FolderViewPopup/CheckNewMessages", folder_item_parent(item) == NULL);
+       SET_SENS("FolderViewPopup/CheckNewFolders",  folder_item_parent(item) == NULL);
+       SET_SENS("FolderViewPopup/RebuildTree",    folder_item_parent(item) == NULL);
+
+       SET_SENS("FolderViewPopup/RemoveMailbox",         folder_item_parent(item) == NULL);
+
+#undef SET_SENS
+}
+
+#define DO_ACTION(name, act)   { if (!strcmp(a_name, name)) act; }
+
+static void update_tree_cb(GtkAction *action, gpointer data)
+{
+       FolderView *folderview = (FolderView *)data;
+       FolderItem *item;
+       const gchar *a_name = gtk_action_get_name(action);
+
+       item = folderview_get_selected_item(folderview);
+       g_return_if_fail(item != NULL);
+
+       summary_show(folderview->summaryview, NULL);
+
+       g_return_if_fail(item->folder != NULL);
+
+       DO_ACTION("FolderViewPopup/CheckNewMessages", folderview_check_new(item->folder));
+       DO_ACTION("FolderViewPopup/CheckNewFolders", folderview_rescan_tree(item->folder, FALSE));
+       DO_ACTION("FolderViewPopup/RebuildTree", folderview_rescan_tree(item->folder, TRUE));
+}
+
+static void add_mailbox(GtkAction *action, gpointer callback_data)
+{
+       MainWindow *mainwin = (MainWindow *) callback_data;
+       gchar *path, *basename;
+       Folder *folder;
+
+       path = input_dialog(_("Add mailbox"),
+                           _("Input the location of mailbox.\n"
+                             "If the existing mailbox is specified, it will be\n"
+                             "scanned automatically."),
+                           "Mail");
+       if (!path) return;
+       if (folder_find_from_path(path)) {
+               alertpanel_error(_("The mailbox `%s' already exists."), path);
+               g_free(path);
+               return;
+       }
+       basename = g_path_get_basename(path);
+       folder = folder_new(folder_get_class_from_string("mailmbox"), 
+                           !strcmp(path, "Mail") ? _("Mailbox") : basename,
+                           path);
+       g_free(basename);                           
+       g_free(path);
+
+
+       if (folder->klass->create_tree(folder) < 0) {
+               alertpanel_error(_("Creation of the mailbox failed.\n"
+                                  "Maybe some files already exist, or you don't have the permission to write there."));
+               folder_destroy(folder);
+               return;
+       }
+
+       folder_add(folder);
+       folder_scan_tree(folder, TRUE);
+
+       folderview_set(mainwin->folderview);
+
+       return;
+}
+
+static void new_folder_cb(GtkAction *action, gpointer data)
+{
+       FolderView *folderview = (FolderView *)data;
+       GtkCMCTree *ctree = GTK_CMCTREE(folderview->ctree);
+       FolderItem *item;
+       FolderItem *new_item;
+       gchar *new_folder;
+       gchar *name;
+       gchar *p;
+
+       if (!folderview->selected) return;
+
+       item = gtk_cmctree_node_get_row_data(ctree, folderview->selected);
+       g_return_if_fail(item != NULL);
+       g_return_if_fail(item->folder != NULL);
+
+       new_folder = input_dialog(_("New folder"),
+                                 _("Input the name of new folder:"),
+                                 _("NewFolder"));
+       if (!new_folder) return;
+       AUTORELEASE_STR(new_folder, {g_free(new_folder); return;});
+
+       p = strchr(new_folder, G_DIR_SEPARATOR);
+       if (p == NULL)
+               p = strchr(new_folder, '.');
+       if (p) {
+               alertpanel_error(_("`%c' can't be included in folder name."),
+                                p[0]);
+               return;
+       }
+
+       name = trim_string(new_folder, 32);
+       AUTORELEASE_STR(name, {g_free(name); return;});
+
+       /* find whether the directory already exists */
+       p = g_strconcat(item->path ? item->path : "", ".", new_folder, NULL);
+       if (folder_find_child_item_by_name(item, p)) {
+               g_free(p);
+               alertpanel_error(_("The folder `%s' already exists."), name);
+               return;
+       }
+       g_free(p);
+
+       new_item = folder_create_folder(item, new_folder);
+       if (!new_item) {
+               alertpanel_error(_("Can't create the folder `%s'."), name);
+               return;
+       }
+
+       folder_write_list();
+}
+
+static void remove_mailbox_cb(GtkAction *action, gpointer data)
+{
+       FolderView *folderview = (FolderView *)data;
+       FolderItem *item;
+       gchar *name;
+       gchar *message;
+       AlertValue avalue;
+
+       item = folderview_get_selected_item(folderview);
+       g_return_if_fail(item != NULL);
+       g_return_if_fail(item->folder != NULL);
+       if (folder_item_parent(item)) return;
+
+       name = trim_string(item->folder->name, 32);
+       message = g_strdup_printf
+               (_("Really remove the mailbox `%s' ?\n"
+                  "(The messages are NOT deleted from the disk)"), name);
+       avalue = alertpanel_full(_("Remove mailbox"), message,
+                                GTK_STOCK_CANCEL, _("_Remove"), NULL, FALSE,
+                                NULL, ALERT_WARNING, G_ALERTDEFAULT);
+       g_free(message);
+       g_free(name);
+       if (avalue != G_ALERTALTERNATE) return;
+
+       folderview_unselect(folderview);
+       summary_clear_all(folderview->summaryview);
+
+       folder_destroy(item->folder);
+}
+
+static void delete_folder_cb(GtkAction *action, gpointer data)
+{
+       FolderView *folderview = (FolderView *)data;
+       GtkCMCTree *ctree = GTK_CMCTREE(folderview->ctree);
+       FolderItem *item;
+       gchar *message, *name;
+       AlertValue avalue;
+       gchar *old_path;
+       gchar *old_id;
+
+       item = folderview_get_selected_item(folderview);
+       g_return_if_fail(item != NULL);
+       g_return_if_fail(item->path != NULL);
+       g_return_if_fail(item->folder != NULL);
+
+       name = trim_string(item->name, 32);
+       AUTORELEASE_STR(name, {g_free(name); return;});
+       message = g_strdup_printf
+               (_("All folder(s) and message(s) under `%s' will be deleted.\n"
+                  "Do you really want to delete?"), name);
+       avalue = alertpanel_full(_("Delete folder"), message,
+                                GTK_STOCK_CANCEL, GTK_STOCK_DELETE, NULL, FALSE,
+                                NULL, ALERT_NOTICE, G_ALERTDEFAULT);
+       g_free(message);
+       if (avalue != G_ALERTALTERNATE) return;
+
+       Xstrdup_a(old_path, item->path, return);
+       old_id = folder_item_get_identifier(item);
+
+       if (folderview->opened == folderview->selected ||
+           gtk_cmctree_is_ancestor(ctree,
+                                 folderview->selected,
+                                 folderview->opened)) {
+               summary_clear_all(folderview->summaryview);
+               folderview->opened = NULL;
+       }
+
+       if (item->folder->klass->remove_folder(item->folder, item) < 0) {
+               alertpanel_error(_("Can't remove the folder `%s'."), name);
+               if (folderview->opened == folderview->selected)
+                       summary_show(folderview->summaryview,
+                                    folderview->summaryview->folder_item);
+               g_free(old_id);
+               return;
+       }
+
+       folder_write_list();
+
+       prefs_filtering_delete_path(old_id);
+       g_free(old_id);
+
+}
+
+static void move_folder_cb(GtkAction *action, gpointer data)
+{
+       FolderView *folderview = (FolderView *)data;
+       FolderItem *from_folder = NULL, *to_folder = NULL;
+
+       from_folder = folderview_get_selected_item(folderview);
+       if (!from_folder || from_folder->folder->klass != claws_mailmbox_get_class())
+               return;
+
+       to_folder = foldersel_folder_sel(from_folder->folder, FOLDER_SEL_MOVE, NULL, FALSE);
+       if (!to_folder)
+               return;
+
+       folderview_move_folder(folderview, from_folder, to_folder, 0);
+}
+
+static void copy_folder_cb(GtkAction *action, gpointer data)
+{
+       FolderView *folderview = (FolderView *)data;
+       FolderItem *from_folder = NULL, *to_folder = NULL;
+
+       from_folder = folderview_get_selected_item(folderview);
+       if (!from_folder || from_folder->folder->klass != claws_mailmbox_get_class())
+               return;
+
+       to_folder = foldersel_folder_sel(from_folder->folder, FOLDER_SEL_MOVE, NULL, FALSE);
+       if (!to_folder)
+               return;
+
+       folderview_move_folder(folderview, from_folder, to_folder, 1);
+}
+
+static void rename_folder_cb(GtkAction *action, gpointer data)
+{
+       FolderView *folderview = (FolderView *)data;
+       FolderItem *item, *parent;
+       gchar *new_folder;
+       gchar *name;
+       gchar *message;
+       gchar *old_path;
+       gchar *old_id;
+       gchar *new_id;
+       gchar *p;
+
+       item = folderview_get_selected_item(folderview);
+       g_return_if_fail(item != NULL);
+       g_return_if_fail(item->path != NULL);
+       g_return_if_fail(item->folder != NULL);
+
+       name = trim_string(item->name, 32);
+       message = g_strdup_printf(_("Input new name for `%s':"), name);
+       new_folder = input_dialog(_("Rename folder"), message, item->name);
+       g_free(message);
+       g_free(name);
+       if (!new_folder) return;
+       AUTORELEASE_STR(new_folder, {g_free(new_folder); return;});
+
+       p = strchr(new_folder, G_DIR_SEPARATOR);
+       if (p == NULL)
+               p = strchr(new_folder, '.');
+       if (p) {
+               alertpanel_error(_("`%c' can't be included in folder name."),
+                                p[0]);
+               return;
+       }
+
+       parent = folder_item_parent(item);
+       p = g_strconcat(parent->path ? parent->path : "", ".", new_folder, NULL);
+       if (folder_find_child_item_by_name(parent, p)) {
+               name = trim_string(new_folder, 32);
+               alertpanel_error(_("The folder `%s' already exists."), name);
+               g_free(name);
+               return;
+       }
+
+       Xstrdup_a(old_path, item->path, {g_free(new_folder); return;});
+
+       old_id = folder_item_get_identifier(item);
+
+       if (folder_item_rename(item, new_folder) < 0) {
+               alertpanel_error(_("The folder could not be renamed.\n"
+                                  "The new folder name is not allowed."));
+               g_free(old_id);
+               return;
+       }
+
+       new_id = folder_item_get_identifier(item);
+       prefs_filtering_rename_path(old_id, new_id);
+       account_rename_path(old_id, new_id);
+       prefs_actions_rename_path(old_id, new_id);
+
+       g_free(old_id);
+       g_free(new_id);
+
+       folder_item_prefs_save_config_recursive(item);
+       folder_write_list();
+}
+
diff --git a/src/plugins/mailmbox/plugin_gtk.h b/src/plugins/mailmbox/plugin_gtk.h
new file mode 100644 (file)
index 0000000..f1e3c46
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * mailmbox Plugin -- mbox support for Sylpheed
+ * Copyright (C) 2003-2005 Christoph Hohmann, 
+ *                        Hoa v. Dinh, 
+ *                        Alfons Hoogervorst
+ *
+ * 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 3 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 __PLUGIN_GTK_H__
+#define __PLUGIN_GTK_H__
+
+#include <glib.h>
+
+gint plugin_gtk_init(gchar **error);
+void plugin_gtk_done(void);
+
+#endif
diff --git a/src/plugins/newmail/Makefile.am b/src/plugins/newmail/Makefile.am
new file mode 100644 (file)
index 0000000..479bd4e
--- /dev/null
@@ -0,0 +1,19 @@
+plugindir = $(pkglibdir)/plugins
+
+plugin_LTLIBRARIES = newmail.la
+
+newmail_la_SOURCES = \
+       newmail.c
+
+newmail_la_LDFLAGS = \
+       -avoid-version -module
+
+AM_CPPFLAGS = \
+        $(CLAWS_MAIL_CFLAGS) \
+       $(GLIB_CFLAGS) \
+       $(GTK_CFLAGS)
+
+INCLUDES = \
+       -I$(top_srcdir)/src \
+       -I$(top_srcdir)/src/common \
+       -I$(top_srcdir)/src/gtk
diff --git a/src/plugins/newmail/newmail.c b/src/plugins/newmail/newmail.c
new file mode 100644 (file)
index 0000000..8d7c40e
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * newmail - A plugin for Claws Mail
+ *
+ * Copyright (C) 2005-2005 H.Merijn Brand and the Claws Mail Team
+ *
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2012 the Claws Mail Team
+ *
+ * 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 3 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 <errno.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "version.h"
+#include "claws.h"
+#include "plugin.h"
+#include "utils.h"
+#include "hooks.h"
+#include "procmsg.h"
+
+#include <inttypes.h>
+
+#include "plugin.h"
+
+static guint hook_id;
+
+static FILE *NewLog   = NULL;
+static char *LogName  = NULL;
+static int   truncLog = 1;
+
+static gchar *defstr (gchar *s)
+{
+    return s ? s : "(null)";
+    } /* defstr */
+
+gboolean newmail_hook (gpointer source, gpointer data)
+{
+    auto MsgInfo    *msginfo = (MsgInfo *)source;
+    auto FolderItem *tof;
+
+    if (!msginfo) return (FALSE);
+
+    tof = msginfo->folder;
+    (void)fprintf (NewLog, "---\n"
+       "Date:\t%s\n"
+       "Subject:\t%s\n"
+       "From:\t%s\n"
+       "To:\t%s\n"
+       "Cc:\t%s\n"
+       "Size:\t%jd\n"
+       "Path:\t%s\n"
+       "Message:\t%d\n"
+       "Folder:\t%s\n",
+           defstr (msginfo->date),
+           defstr (msginfo->subject),
+           defstr (msginfo->from),
+           defstr (msginfo->to),
+           defstr (msginfo->cc),
+           (intmax_t) msginfo->size,
+           defstr (procmsg_get_message_file_path (msginfo)),
+           msginfo->msgnum,
+           tof ? defstr (tof->name) : "(null)");
+
+    return (FALSE);
+    } /* newmail_hook */
+
+gboolean plugin_done (void)
+{
+    if (NewLog) {
+       (void)fclose (NewLog);
+       NewLog  = NULL;
+       LogName = NULL;
+       }
+    hooks_unregister_hook (MAIL_POSTFILTERING_HOOKLIST, hook_id);
+
+    printf (_("Newmail plugin unloaded\n"));
+    return TRUE;
+    } /* plugin_done */
+
+gint plugin_init (gchar **error)
+{
+       if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
+                               VERSION_NUMERIC, _("NewMail"), error))
+               return -1;
+
+       hook_id = hooks_register_hook (MAIL_POSTFILTERING_HOOKLIST, newmail_hook, NULL);
+  if (hook_id == -1) {
+               *error = g_strdup (_("Failed to register newmail hook"));
+               return (-1);
+       }
+
+    if (!NewLog) {
+       auto char *mode = truncLog ? "w" : "a";
+       if (!LogName) {
+           auto size_t l;
+           auto char   name[260];
+           (void)snprintf (name, 256, "%s/Mail/NewLog", getenv ("HOME"));
+           l = strlen (name);
+           if (l > 255 || !(LogName = (char *)malloc (l + 1))) {
+               *error = g_strdup (_("Cannot load plugin NewMail\n"
+                                    "$HOME is too long\n"));
+               plugin_done ();
+               return (-1);
+               }
+           (void)strcpy (LogName, name);
+           }
+       if (!(NewLog = fopen (LogName, mode))) {
+           *error = g_strdup (sys_errlist[errno]);
+           plugin_done ();
+           return (-1);
+           }
+       setbuf (NewLog, NULL);
+       }
+
+    printf (_("Newmail plugin loaded\n"
+              "Message header summaries written to %s\n"), LogName);
+    return (0);
+    } /* plugin_init */
+
+const gchar *plugin_name (void)
+{
+    return _("NewMail");
+    } /* plugin_name */
+
+const gchar *plugin_desc (void)
+{
+    return _("This Plugin writes a header summary to a log file for each "
+            "mail received after sorting.\n\n"
+            "Default is ~/Mail/NewLog");
+    } /* plugin_desc */
+
+const gchar *plugin_type (void)
+{
+    return ("Common");
+    } /* plugin_type */
+
+const gchar *plugin_licence (void)
+{
+    return ("GPL3+");
+    } /* plugin_licence */
+
+const gchar *plugin_version (void)
+{
+    return (VERSION);
+    } /* plugin_version */
+
+struct PluginFeature *plugin_provides(void)
+{
+       static struct PluginFeature features[] = 
+               { {PLUGIN_NOTIFIER, N_("Log file")},
+                 {PLUGIN_NOTHING, NULL}};
+       return features;
+}
diff --git a/src/plugins/newmail/placeholder.txt b/src/plugins/newmail/placeholder.txt
deleted file mode 100644 (file)
index 3b94f91..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Placeholder
diff --git a/src/plugins/notification/Makefile.am b/src/plugins/notification/Makefile.am
new file mode 100644 (file)
index 0000000..ea3627b
--- /dev/null
@@ -0,0 +1,106 @@
+if BUILD_HOTKEYS
+hotkey_lib = libcmnpgtkhotkey.la
+hotkey_lib_path = $(top_builddir)/src/plugins/notification/gtkhotkey/libcmnpgtkhotkey.la
+else
+hotkey_lib =
+hotkey_lib_path =
+endif
+
+
+EXTRA_DIST = claws.def plugin.def version.rc
+CFLAGS += "-Wall"
+
+if OS_WIN32
+
+LTRCCOMPILE = $(LIBTOOL) --mode=compile --tag=RC $(RC) \
+     `echo $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) | \
+     sed -e 's/-I/--include-dir /g;s/-D/--define /g'`
+
+%.lo : %.rc
+       $(LTRCCOMPILE) -i $< -o $@
+
+plugin_res = version.lo
+plugin_res_ldflag = -Wl,.libs/version.o
+
+export_symbols = -export-symbols $(srcdir)/plugin.def
+
+plugin_deps = libclaws.a $(plugin_res) plugin.def $(hotkey_lib) 
+
+libclaws.a: claws.def
+       $(DLLTOOL) --output-lib $@ --def $<
+
+plugin_ldadd = -L. -lclaws
+
+else
+plugin_res =
+plugin_res_ldflag =
+export_symbols =
+plugin_deps =
+plugin_ldadd =
+endif
+
+if PLATFORM_WIN32
+no_undefined = -no-undefined
+else
+no_undefined =
+endif
+
+if CYGWIN
+cygwin_export_lib = -L$(top_builddir)/src -lclaws-mail
+else
+cygwin_export_lib = 
+endif
+
+plugindir = $(pkglibdir)/plugins
+
+plugin_LTLIBRARIES = notification_plugin.la
+
+notification_plugin_la_SOURCES = \
+       gettext.h \
+       notification_plugin.c notification_plugin.h \
+       notification_core.c notification_core.h \
+       notification_popup.c notification_popup.h \
+       notification_banner.c notification_banner.h \
+       notification_command.c notification_command.h \
+       notification_lcdproc.c notification_lcdproc.h \
+       notification_trayicon.c notification_trayicon.h \
+       notification_indicator.c notification_indicator.h \
+       notification_hotkeys.c notification_hotkeys.h \
+       notification_prefs.c notification_prefs.h \
+       notification_foldercheck.c notification_foldercheck.h \
+       notification_pixbuf.c notification_pixbuf.h \
+       raw_claws_mail_logo_64x64.h
+
+notification_plugin_la_LDFLAGS = \
+       $(plugin_res_ldflag) $(no_undefined) $(export_symbols) \
+       -avoid-version -module
+
+notification_plugin_la_DEPENDENCIES = $(plugin_deps) \
+       $(hotkey_lib_path)
+
+INCLUDES = \
+       -I$(top_srcdir)/src/plugins/notification/gtkhotkey \
+       -I$(top_srcdir)/src \
+       -I$(top_srcdir)/src/common \
+       -I$(top_srcdir)/src/gtk
+
+
+notification_plugin_la_LIBADD = $(plugin_ldadd) $(cygwin_export_lib) \
+        $(GTK_LIBS)  \
+       $(libnotify_LIBS) \
+       $(libindicate_LIBS) \
+       $(libcanberra_gtk_LIBS) \
+       $(hotkey_lib_path)
+
+notification_plugin_la_CPPFLAGS = \
+       $(NOTIFY_CFLAGS) \
+       $(CLAWS_MAIL_CFLAGS) \
+       $(GLIB_CFLAGS) \
+       $(GTK_CFLAGS) \
+       -DLOCALEDIR=\""$(localedir)"\" \
+       $(libnotify_CFLAGS) \
+       $(libindicate_CFLAGS) \
+       $(libcanberra_gtk_CFLAGS)
+
+clean-local:
+       rm -f libclaws.a
diff --git a/src/plugins/notification/claws.def b/src/plugins/notification/claws.def
new file mode 100644 (file)
index 0000000..be31c0a
--- /dev/null
@@ -0,0 +1,73 @@
+LIBRARY CLAWS-MAIL.EXE
+EXPORTS
+get_locale_dir
+check_plugin_version
+debug_print_real
+debug_srcname
+hooks_register_hook
+hooks_unregister_hook
+account_get_list
+addressbook_open
+alertpanel
+app_will_exit
+claws_is_exiting
+claws_is_starting
+compose_mail_cb
+compose_new
+compose_reply_from_messageview
+execute_command_line
+file_exist
+folder_find_item_from_identifier
+folder_func_to_all_folders
+folder_get_list
+folder_has_parent_of_type
+folder_item_get_identifier
+folder_item_get_msg_list
+folder_item_get_name
+get_rc_dir
+gtkut_convert_int_to_gdk_color
+gtkut_stock_button_set_create
+gtkut_window_new
+inc_all_account_mail_cb
+main_set_show_at_startup
+mainwindow_get_mainwindow
+main_window_hide
+main_window_empty_trash
+mainwindow_is_obscured
+mainwindow_jump_to
+main_window_show
+main_window_toggle_work_offline
+manage_window_destroy
+manage_window_focus_in
+manage_window_focus_out
+manage_window_set_transient
+manage_window_unmap
+cm_menu_create_action_group_full
+gtkut_ui_manager
+cm_menu_create_action_group
+cm_toggle_menu_set_active
+cm_menu_set_sensitive
+prefs_common_get_prefs
+prefs_common_translated_header_name
+prefs_file_close
+prefs_file_close_revert
+prefs_gtk_register_page
+prefs_gtk_unregister_page
+prefs_read_config
+prefs_set_block_label
+prefs_set_default
+prefs_write_open
+prefs_write_param
+procmsg_get_message_file_path
+procmsg_msg_list_free
+stock_pixbuf_gdk
+stock_pixbuf_gdk follow
+strcmp2
+xml_attr_new
+xml_file_put_xml_decl
+xml_free_tree
+xml_node_new
+xml_parse_file
+xml_tag_add_attr
+xml_tag_new
+xml_write_tree
diff --git a/src/plugins/notification/notification_banner.c b/src/plugins/notification/notification_banner.c
new file mode 100644 (file)
index 0000000..9205c17
--- /dev/null
@@ -0,0 +1,503 @@
+/* Notification plugin for Claws Mail
+ * Copyright (C) 2005-2008 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_BANNER
+
+#include <gtk/gtk.h>
+
+#include "gtk/gtkutils.h"
+
+#include "prefs_common.h"
+#include "mainwindow.h"
+#include "menu.h"
+#include "procmsg.h"
+#include "messageview.h"
+#include "compose.h"
+#include "menu.h"
+
+#include "notification_core.h"
+#include "notification_prefs.h"
+#include "notification_banner.h"
+
+typedef struct {
+  GtkWidget *table;
+} NotificationBannerEntry;
+
+typedef struct {
+  GtkWidget *window;
+  GtkWidget *scrolled_win;
+       GtkWidget *viewport;
+
+  NotificationBannerEntry *entries;
+  guint timeout_id;
+  gboolean scrolling;
+} NotificationBanner;
+
+typedef struct {
+  gint banner_width;
+  GtkAdjustment *adj;
+} ScrollingData;
+
+
+static void       notification_banner_create(GSList*);
+static gboolean   scroller(gpointer data);
+static GtkWidget* create_entrybox(GSList*);
+static gboolean   notification_banner_configure(GtkWidget*, GdkEventConfigure*,
+                                               gpointer);
+static gboolean notification_banner_swap_colors(GtkWidget*,GdkEventCrossing*,gpointer);
+static gboolean notification_banner_button_press(GtkWidget*,GdkEventButton*,gpointer);
+static void notification_banner_show_popup(GtkWidget*,GdkEventButton*);
+static void notification_banner_popup_done(GtkMenuShell*,gpointer);
+
+static void banner_menu_reply_cb(GtkAction *,gpointer);
+
+
+static NotificationBanner banner;
+static ScrollingData      sdata;
+
+static GtkWidget *banner_popup;
+static GtkUIManager *banner_ui_manager;
+static GtkActionGroup *banner_action_group;
+
+static gboolean banner_popup_open = FALSE;
+
+static MsgInfo *current_msginfo = NULL;
+
+/* Corresponding mutexes */
+G_LOCK_DEFINE_STATIC(banner);
+G_LOCK_DEFINE_STATIC(sdata);
+
+static GtkActionEntry banner_popup_entries[] = 
+{
+       {"BannerPopup",         NULL, "BannerPopup" },
+       {"BannerPopup/Reply",   NULL, N_("_Reply"), NULL, NULL, G_CALLBACK(banner_menu_reply_cb) },
+};
+
+
+void notification_banner_show(GSList *msg_list)
+{
+  G_LOCK(banner);
+  if((notify_config.banner_show != NOTIFY_BANNER_SHOW_NEVER) &&
+     (g_slist_length(msg_list) ||
+      (notify_config.banner_show == NOTIFY_BANNER_SHOW_ALWAYS)))
+    notification_banner_create(msg_list);
+  else 
+    notification_banner_destroy();
+  G_UNLOCK(banner);
+}
+
+void notification_banner_destroy(void)
+{
+  if(banner.window) {
+    if(banner.entries) {
+      g_free(banner.entries);
+      banner.entries = NULL;
+    }
+    gtk_widget_destroy(banner.window);
+    banner.window = NULL;
+    G_LOCK(sdata);
+    sdata.adj = NULL;
+    sdata.banner_width = 0;
+    G_UNLOCK(sdata);
+    if(banner.timeout_id) {
+      g_source_remove(banner.timeout_id);
+      banner.timeout_id = 0;
+    }
+  }
+}
+
+static void notification_banner_create(GSList *msg_list)
+{
+  GtkRequisition requisition, requisition_after;
+       GtkWidget *viewport;
+       GtkWidget *hbox;
+       GtkWidget *entrybox;
+       GdkColor bg;
+       gint banner_width;
+
+  /* Window */
+  if(!banner.window) {
+
+    banner.window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "notification_banner");
+    gtk_window_set_decorated(GTK_WINDOW(banner.window), FALSE);
+               if(notify_config.banner_width > 0)
+                       gtk_widget_set_size_request(banner.window, notify_config.banner_width, -1);
+               else
+                       gtk_widget_set_size_request(banner.window, gdk_screen_width(), -1);
+    gtk_window_set_keep_above(GTK_WINDOW(banner.window), TRUE);
+    gtk_window_set_accept_focus(GTK_WINDOW(banner.window), FALSE);
+    gtk_window_set_skip_taskbar_hint(GTK_WINDOW(banner.window), TRUE);
+    gtk_window_move(GTK_WINDOW(banner.window),
+                                                                               notify_config.banner_root_x, notify_config.banner_root_y);
+    g_signal_connect(banner.window, "configure-event",
+                                                                                G_CALLBACK(notification_banner_configure), NULL);
+  }
+  else {
+    if(banner.entries) {
+      g_free(banner.entries);
+      banner.entries = NULL;
+    }
+    gtk_widget_destroy(banner.scrolled_win);
+  }
+  if(notify_config.banner_sticky)
+    gtk_window_stick(GTK_WINDOW(banner.window));
+  else
+    gtk_window_unstick(GTK_WINDOW(banner.window));
+
+  /* Scrolled window */
+  banner.scrolled_win = gtk_scrolled_window_new(NULL, NULL);
+  gtk_container_add(GTK_CONTAINER(banner.window), banner.scrolled_win);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(banner.scrolled_win),
+                                                                                                                                GTK_POLICY_NEVER, GTK_POLICY_NEVER);
+
+       /* Viewport */
+       viewport = gtk_viewport_new(NULL,NULL);
+       banner.viewport = viewport;
+       gtk_container_add(GTK_CONTAINER(banner.scrolled_win),viewport);
+       if(notify_config.banner_enable_colors) {
+               gtkut_convert_int_to_gdk_color(notify_config.banner_color_bg,&bg);
+               gtk_widget_modify_bg(viewport,GTK_STATE_NORMAL,&bg);
+       }
+
+  /* Hbox */
+  hbox = gtk_hbox_new(FALSE, 5);
+       gtk_container_add(GTK_CONTAINER(viewport),hbox);
+
+  /* Entrybox */
+  entrybox = create_entrybox(msg_list);
+  gtk_box_pack_start(GTK_BOX(hbox), entrybox, FALSE, FALSE, 0);
+
+  gtk_widget_show_all(banner.window);
+
+  /* Scrolling */
+  gtk_widget_size_request(hbox, &requisition);
+  if(notify_config.banner_width > 0)
+               banner_width = notify_config.banner_width;
+       else
+               banner_width = gdk_screen_width();
+  if(requisition.width > banner_width) {
+    /* Line is too big for screen! */
+    /* Double the entrybox into hbox */
+    GtkWidget *separator, *second_entrybox;
+
+    separator = gtk_vseparator_new();
+    gtk_box_pack_start(GTK_BOX(hbox), separator,
+                      FALSE, FALSE, 0);
+    second_entrybox = create_entrybox(msg_list);
+    gtk_box_pack_start(GTK_BOX(hbox), second_entrybox, FALSE, FALSE, 0);
+
+    gtk_widget_show_all(banner.window);
+    gtk_widget_size_request(hbox, &requisition_after);
+
+    G_LOCK(sdata);
+    sdata.banner_width = requisition_after.width - requisition.width;
+    sdata.adj =
+      gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW
+                                         (banner.scrolled_win));
+    G_UNLOCK(sdata);
+    
+    banner.scrolling = TRUE;
+    if(banner.timeout_id) {
+      g_source_remove(banner.timeout_id);
+      banner.timeout_id = 0;
+    }
+    banner.timeout_id = 
+      g_timeout_add(notify_config.banner_speed, scroller, NULL);
+  }
+  else {
+    banner.scrolling = FALSE;
+    if(banner.timeout_id) {
+      g_source_remove(banner.timeout_id);
+      banner.timeout_id = 0;
+    }
+    G_LOCK(sdata);
+    sdata.banner_width = 0;
+    sdata.adj = NULL;
+    G_UNLOCK(sdata);
+  }
+
+       /* menu */
+  banner_ui_manager = gtk_ui_manager_new();
+  banner_action_group = cm_menu_create_action_group_full(banner_ui_manager,"BannerPopup", banner_popup_entries,
+                       G_N_ELEMENTS(banner_popup_entries), (gpointer)NULL);
+  MENUITEM_ADDUI_MANAGER(banner_ui_manager, "/", "Menus", "Menus", GTK_UI_MANAGER_MENUBAR)
+  MENUITEM_ADDUI_MANAGER(banner_ui_manager, "/Menus", "BannerPopup", "BannerPopup", GTK_UI_MANAGER_MENU)
+  MENUITEM_ADDUI_MANAGER(banner_ui_manager, "/Menus/BannerPopup", "Reply", "BannerPopup/Reply", GTK_UI_MANAGER_MENUITEM)
+
+  banner_popup = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
+                               gtk_ui_manager_get_widget(banner_ui_manager, "/Menus/BannerPopup")) );
+       g_signal_connect(banner_popup,"selection-done",
+                                                                        G_CALLBACK(notification_banner_popup_done), NULL);
+}
+
+static gboolean notification_banner_configure(GtkWidget *widget,
+                                             GdkEventConfigure *event,
+                                             gpointer data)
+{
+  gtk_window_get_position(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
+                         &(notify_config.banner_root_x),
+                         &(notify_config.banner_root_y));
+  return TRUE;
+}
+
+static gboolean scroller(gpointer data)
+{
+       // don't scroll during popup open
+       if(banner_popup_open)
+               return banner.scrolling;
+
+       while(gtk_events_pending())
+               gtk_main_iteration();
+  G_LOCK(sdata);
+  if(sdata.adj && GTK_IS_ADJUSTMENT(sdata.adj)) {
+    if(sdata.adj->value != sdata.banner_width)
+      gtk_adjustment_set_value(sdata.adj, sdata.adj->value + 1);
+    else
+      gtk_adjustment_set_value(sdata.adj, 0);
+               gtk_adjustment_value_changed(sdata.adj);
+  }
+  G_UNLOCK(sdata);
+       while(gtk_events_pending())
+               gtk_main_iteration();
+  return banner.scrolling;
+}
+
+static GtkWidget* create_entrybox(GSList *msg_list)
+{
+  GtkWidget *entrybox;
+  GdkColor fg;
+       GdkColor bg;
+  gint list_length;
+
+  list_length = g_slist_length(msg_list);
+
+  if(notify_config.banner_enable_colors) {
+    gtkut_convert_int_to_gdk_color(notify_config.banner_color_bg,&bg);
+               gtkut_convert_int_to_gdk_color(notify_config.banner_color_fg,&fg);
+  }
+
+  if(banner.entries) {
+    g_free(banner.entries);
+    banner.entries = NULL;
+  }
+
+  entrybox = gtk_hbox_new(FALSE, 5);
+  if(list_length) {
+    GSList *walk;
+    gint ii = 0;
+    banner.entries = g_new(NotificationBannerEntry, list_length);
+    for(walk = msg_list; walk != NULL; walk = g_slist_next(walk)) {
+      GtkWidget *label1;
+      GtkWidget *label2;
+      GtkWidget *label3;
+      GtkWidget *label4;
+      GtkWidget *label5;
+      GtkWidget *label6;
+                       GtkWidget *ebox;
+      CollectedMsg *cmsg = walk->data;
+      
+      if(ii > 0) {
+                               GtkWidget *separator;
+                               separator = gtk_vseparator_new();
+                               gtk_box_pack_start(GTK_BOX(entrybox), separator, FALSE, FALSE, 0);
+      }
+
+                       ebox = gtk_event_box_new();
+                       gtk_box_pack_start(GTK_BOX(entrybox), ebox, FALSE, FALSE, 0);
+                       gtk_widget_set_events(ebox,
+                                                                                                               GDK_POINTER_MOTION_MASK |
+                                                                                                               GDK_BUTTON_PRESS_MASK);
+
+                       if(notify_config.banner_enable_colors)
+                               gtk_widget_modify_bg(ebox,GTK_STATE_NORMAL,&bg);
+
+      banner.entries[ii].table = gtk_table_new(3, 2, FALSE);
+                       gtk_container_add(GTK_CONTAINER(ebox),banner.entries[ii].table);
+                       g_signal_connect(ebox, "enter-notify-event",
+                                                                                        G_CALLBACK(notification_banner_swap_colors),
+                                                                                        banner.entries[ii].table);
+                       g_signal_connect(ebox, "leave-notify-event",
+                                                                                        G_CALLBACK(notification_banner_swap_colors),
+                                                                                        banner.entries[ii].table);
+                       g_signal_connect(ebox, "button-press-event",
+                                                                                        G_CALLBACK(notification_banner_button_press),
+                                                                                        cmsg);
+
+      label1 = gtk_label_new(prefs_common_translated_header_name("From:"));
+      gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
+      gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table), 
+                               label1, 0, 1, 0, 1);
+      label2 = gtk_label_new(prefs_common_translated_header_name("Subject:"));
+      gtk_misc_set_alignment(GTK_MISC(label2), 0, 0.5);
+      gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
+                               label2, 0, 1, 1, 2);
+      label3 = gtk_label_new(_("Folder:"));
+      gtk_misc_set_alignment(GTK_MISC(label3), 0, 0.5);
+      gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
+                               label3, 0, 1, 2, 3);
+      
+      label4 = gtk_label_new(cmsg->from);
+      gtk_misc_set_alignment(GTK_MISC(label4), 0, 0.5);
+      gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
+                               label4, 1, 2, 0, 1);
+      label5 = gtk_label_new(cmsg->subject);
+      gtk_misc_set_alignment(GTK_MISC(label5), 0, 0.5);
+      gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
+                               label5, 1, 2, 1, 2);
+      label6 = gtk_label_new(cmsg->folderitem_name);
+      gtk_misc_set_alignment(GTK_MISC(label6), 0, 0.5);
+      gtk_table_attach_defaults(GTK_TABLE(banner.entries[ii].table),
+                               label6, 1, 2, 2, 3);
+      gtk_table_set_col_spacings(GTK_TABLE(banner.entries[ii].table), 5);
+                       ii++;
+      /* Color */
+      if(notify_config.banner_enable_colors) {
+                               gtk_widget_modify_fg(label1,GTK_STATE_NORMAL,&fg);
+                               gtk_widget_modify_fg(label2,GTK_STATE_NORMAL,&fg);
+                               gtk_widget_modify_fg(label3,GTK_STATE_NORMAL,&fg);
+                               gtk_widget_modify_fg(label4,GTK_STATE_NORMAL,&fg);
+                               gtk_widget_modify_fg(label5,GTK_STATE_NORMAL,&fg);
+                               gtk_widget_modify_fg(label6,GTK_STATE_NORMAL,&fg);
+      }
+    }
+  }
+  else {
+    /* We have an empty list -- create an empty dummy element */
+    GtkWidget *label;
+
+    banner.entries = g_new(NotificationBannerEntry, 1);
+    banner.entries[0].table = gtk_table_new(3, 1, FALSE);
+    label = gtk_label_new("");
+    gtk_table_attach_defaults(GTK_TABLE(banner.entries[0].table), 
+                             label, 0, 1, 0, 1);
+    label = gtk_label_new("");
+    gtk_table_attach_defaults(GTK_TABLE(banner.entries[0].table), 
+                             label, 0, 1, 1, 2);
+    label = gtk_label_new("");
+    gtk_table_attach_defaults(GTK_TABLE(banner.entries[0].table), 
+                             label, 0, 1, 2, 3);
+    gtk_box_pack_start(GTK_BOX(entrybox), banner.entries[0].table,
+                      FALSE, FALSE, 0);
+  }
+  return entrybox;
+}
+
+static gboolean notification_banner_swap_colors(GtkWidget *widget,
+                                                                                                                                                                                               GdkEventCrossing *event,
+                                                                                                                                                                                               gpointer data)
+{
+       GList *children;
+       GList *walk;
+       GdkColor *old_bg;
+       
+       children = gtk_container_get_children(GTK_CONTAINER(data));
+
+       old_bg = gdk_color_copy(&(gtk_widget_get_style(widget)->bg[GTK_STATE_NORMAL]));
+       if(children)
+               gtk_widget_modify_bg(widget,GTK_STATE_NORMAL,
+                                                                                                &(gtk_widget_get_style(GTK_WIDGET(children->data))->fg[GTK_STATE_NORMAL]));
+       
+       for(walk = children; walk; walk = walk->next)
+               gtk_widget_modify_fg(GTK_WIDGET(walk->data),GTK_STATE_NORMAL,old_bg);
+       
+       g_list_free(children);
+       gdk_color_free(old_bg);
+       return FALSE;
+}
+
+static gboolean notification_banner_button_press(GtkWidget *widget,
+                                                                                                                                                                                                GdkEventButton *button,
+                                                                                                                                                                                                gpointer data)
+{
+       gboolean done;
+       done = FALSE;
+       if(button->type == GDK_BUTTON_PRESS) {
+               CollectedMsg *cmsg = (CollectedMsg*) data;
+    if(button->button == 1) {
+                       /* jump to that message */
+                       if(cmsg->msginfo) {
+                               gchar *path;
+                               path = procmsg_get_message_file_path(cmsg->msginfo);
+                               mainwindow_jump_to(path, TRUE);
+                               g_free(path);
+                       }
+                       done = TRUE;
+               }
+               else if(button->button == 2) {
+      gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)), 
+                                                                                                                                button->button, button->x_root, button->y_root,
+                                                                                                                                button->time);
+               }
+               else if(button->button == 3) {
+                       current_msginfo = cmsg->msginfo;
+                       notification_banner_show_popup(widget, button);
+                       banner_popup_open = TRUE;
+                       done = TRUE;
+               }
+       }
+       return done;
+}
+
+static void notification_banner_show_popup(GtkWidget *widget,
+                                                                                                                                                                        GdkEventButton *event)
+{
+  int button, event_time;
+
+  if(event) {
+               button = event->button;
+               event_time = event->time;
+       }
+  else {
+               button = 0;
+               event_time = gtk_get_current_event_time();
+       }
+
+  gtk_menu_popup(GTK_MENU(banner_popup), NULL, NULL, NULL, NULL,
+                                                                button, event_time);
+}
+
+static void notification_banner_popup_done(GtkMenuShell *menushell,
+                                                                                                                                                                        gpointer      user_data)
+{
+       current_msginfo = NULL;
+       banner_popup_open = FALSE;
+}
+
+static void banner_menu_reply_cb(GtkAction *action, gpointer data)
+{
+       MainWindow *mainwin;
+       MessageView *messageview;
+       GSList *msginfo_list = NULL;
+
+       if(!(mainwin = mainwindow_get_mainwindow()))
+               return;
+
+       if(!(messageview = (MessageView*)mainwin->messageview))
+               return;
+
+       g_return_if_fail(current_msginfo);
+
+       msginfo_list = g_slist_prepend(msginfo_list, current_msginfo);
+       compose_reply_from_messageview(messageview, msginfo_list,
+                                      prefs_common_get_prefs()->reply_with_quote ?
+                                      COMPOSE_REPLY_WITH_QUOTE : COMPOSE_REPLY_WITHOUT_QUOTE);
+       g_slist_free(msginfo_list);
+}
+
+#endif /* NOTIFICATION_BANNER */
diff --git a/src/plugins/notification/notification_banner.h b/src/plugins/notification/notification_banner.h
new file mode 100644 (file)
index 0000000..121d3f7
--- /dev/null
@@ -0,0 +1,32 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NOTIFICATION_BANNER_H
+#define NOTIFICATION_BANNER_H NOTIFICATION_BANNER_H
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_BANNER
+
+#define BANNER_SPECIFIC_FOLDER_ID_STR "banner"
+
+void notification_banner_show(GSList*);
+void notification_banner_destroy(void);
+
+#endif /* NOTIFICATION_BANNER */
+
+#endif /* NOTIFICATION_BANNER_H */
diff --git a/src/plugins/notification/notification_command.c b/src/plugins/notification/notification_command.c
new file mode 100644 (file)
index 0000000..e797950
--- /dev/null
@@ -0,0 +1,119 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_COMMAND
+
+#include <string.h>
+#include <glib.h>
+#include "common/utils.h"
+#include "folder.h"
+#include "notification_command.h"
+#include "notification_prefs.h"
+#include "notification_foldercheck.h"
+
+typedef struct {
+  gboolean blocked;
+  guint timeout_id;
+} NotificationCommand;
+
+static gboolean command_timeout_fun(gpointer data);
+
+static NotificationCommand command;
+
+G_LOCK_DEFINE_STATIC(command);
+
+void notification_command_msg(MsgInfo *msginfo)
+{
+  gchar *ret_str, *buf;
+  gsize by_read = 0, by_written = 0;
+  FolderType ftype;
+
+  if(!msginfo || !notify_config.command_enabled || !MSG_IS_NEW(msginfo->flags))
+    return;
+
+  if(!notify_config.command_enabled || !MSG_IS_NEW(msginfo->flags))
+    return;
+
+  if(notify_config.command_folder_specific) {
+    guint id;
+    GSList *list;
+    gchar *identifier;
+    gboolean found = FALSE;
+
+    if(!(msginfo->folder))
+      return;
+
+    identifier = folder_item_get_identifier(msginfo->folder);
+
+    id =
+      notification_register_folder_specific_list(COMMAND_SPECIFIC_FOLDER_ID_STR);
+    list = notification_foldercheck_get_list(id);
+    for(; (list != NULL) && !found; list = g_slist_next(list)) {
+      gchar *list_identifier;
+      FolderItem *list_item = (FolderItem*) list->data;
+      
+      list_identifier = folder_item_get_identifier(list_item);
+      if(!strcmp2(list_identifier, identifier))
+       found = TRUE;
+
+      g_free(list_identifier);
+    }
+    g_free(identifier);
+    
+    if(!found)
+      return;
+  } /* folder specific */
+
+  ftype = msginfo->folder->folder->klass->type;
+
+  buf = g_strdup(notify_config.command_line);
+
+  G_LOCK(command);
+
+  if(!command.blocked) {
+    /* execute command */
+    command.blocked = TRUE;
+    ret_str = g_locale_from_utf8(buf,strlen(buf),&by_read,&by_written,NULL);
+    if(ret_str && by_written) {
+      g_free(buf);
+      buf = ret_str;
+    }
+    execute_command_line(buf, TRUE);
+    g_free(buf);
+  }
+
+  /* block further execution for some time,
+     no matter if it was blocked or not */
+  if(command.timeout_id)
+    g_source_remove(command.timeout_id);
+  command.timeout_id = g_timeout_add(notify_config.command_timeout,
+                                    command_timeout_fun, NULL);    
+  G_UNLOCK(command);
+}
+
+static gboolean command_timeout_fun(gpointer data)
+{
+  G_LOCK(command);
+  command.timeout_id = 0;
+  command.blocked = FALSE;
+  G_UNLOCK(command);
+  return FALSE;
+}
+
+#endif /* NOTIFICATION_COMMAND */
diff --git a/src/plugins/notification/notification_command.h b/src/plugins/notification/notification_command.h
new file mode 100644 (file)
index 0000000..998eabf
--- /dev/null
@@ -0,0 +1,33 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NOTIFICATION_COMMAND_H
+#define NOTIFICATION_COMMAND_H NOTIFICATION_COMMAND_H 
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_COMMAND
+
+#include "procmsg.h"
+
+#define COMMAND_SPECIFIC_FOLDER_ID_STR "command"
+
+void notification_command_msg(MsgInfo*);
+
+#endif /* NOTIFICATION_COMMAND */
+
+#endif /* include guard */
diff --git a/src/plugins/notification/notification_core.c b/src/plugins/notification/notification_core.c
new file mode 100644 (file)
index 0000000..9f2f8dd
--- /dev/null
@@ -0,0 +1,699 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pluginconfig.h"
+
+#include "folder.h"
+#include "folderview.h"
+#include "codeconv.h"
+#include "gtk/gtkutils.h"
+
+#include "notification_core.h"
+#include "notification_plugin.h"
+#include "notification_prefs.h"
+#include "notification_banner.h"
+#include "notification_popup.h"
+#include "notification_command.h"
+#include "notification_lcdproc.h"
+#include "notification_trayicon.h"
+#include "notification_indicator.h"
+
+#ifdef HAVE_LIBCANBERRA_GTK
+# include <canberra-gtk.h>
+#endif
+
+typedef struct {
+  GSList *collected_msgs;
+  GSList *folder_items;
+  gboolean unread_also;
+  gint max_msgs;
+  gint num_msgs;
+} TraverseCollect;
+
+static gboolean notification_traverse_collect(GNode*, gpointer);
+static void     notification_new_unnotified_do_msg(MsgInfo*);
+static gboolean notification_traverse_hash_startup(GNode*, gpointer);
+
+static GHashTable *msg_count_hash;
+static NotificationMsgCount msg_count;
+
+#ifdef HAVE_LIBCANBERRA_GTK
+static gboolean canberra_new_email_is_playing = FALSE;
+#endif
+
+static void msg_count_hash_update_func(FolderItem*, gpointer);
+static void msg_count_update_from_hash(gpointer, gpointer, gpointer);
+static void msg_count_clear(NotificationMsgCount*);
+static void msg_count_add(NotificationMsgCount*,NotificationMsgCount*);
+static void msg_count_copy(NotificationMsgCount*,NotificationMsgCount*);
+
+void notification_core_global_includes_changed(void)
+{
+#ifdef NOTIFICATION_BANNER
+  notification_update_banner();
+#endif
+
+  if(msg_count_hash) {
+    g_hash_table_destroy(msg_count_hash);
+    msg_count_hash = NULL;
+  }
+  notification_update_msg_counts(NULL);
+}
+
+/* Hide/show main window */
+void notification_toggle_hide_show_window(void)
+{
+  MainWindow *mainwin;
+  if((mainwin = mainwindow_get_mainwindow()) == NULL)
+    return;
+
+  if(gtk_widget_get_visible(GTK_WIDGET(mainwin->window))) {
+    if((gdk_window_get_state(GTK_WIDGET(mainwin->window)->window)&GDK_WINDOW_STATE_ICONIFIED)
+       || mainwindow_is_obscured()) {
+      notification_show_mainwindow(mainwin);
+    }
+    else {
+      main_window_hide(mainwin);
+    }
+  }
+  else {
+    notification_show_mainwindow(mainwin);
+  }
+}
+
+void notification_update_msg_counts(FolderItem *removed_item)
+{
+  if(!msg_count_hash)
+    msg_count_hash = g_hash_table_new_full(g_str_hash,g_str_equal,
+                                          g_free,g_free);
+
+  folder_func_to_all_folders(msg_count_hash_update_func, msg_count_hash);
+
+  if(removed_item) {
+    gchar *identifier;
+    identifier = folder_item_get_identifier(removed_item);
+    if(identifier) {
+      g_hash_table_remove(msg_count_hash, identifier);
+      g_free(identifier);
+    }
+  }
+  msg_count_clear(&msg_count);
+  g_hash_table_foreach(msg_count_hash, msg_count_update_from_hash, NULL);
+#ifdef NOTIFICATION_LCDPROC
+  notification_update_lcdproc();
+#endif
+#ifdef NOTIFICATION_TRAYICON
+  notification_update_trayicon();
+#endif
+#ifdef NOTIFICATION_INDICATOR
+  notification_update_indicator();
+#endif
+  notification_update_urgency_hint();
+}
+
+static void msg_count_clear(NotificationMsgCount *count)
+{
+  count->new_msgs          = 0;
+  count->unread_msgs       = 0;
+  count->unreadmarked_msgs = 0;
+  count->marked_msgs       = 0;
+  count->total_msgs        = 0;
+}
+
+/* c1 += c2 */
+static void msg_count_add(NotificationMsgCount *c1,NotificationMsgCount *c2)
+{
+  c1->new_msgs          += c2->new_msgs;
+  c1->unread_msgs       += c2->unread_msgs;
+  c1->unreadmarked_msgs += c2->unreadmarked_msgs;
+  c1->marked_msgs       += c2->marked_msgs;
+  c1->total_msgs        += c2->total_msgs;
+}
+
+/* c1 = c2 */
+static void msg_count_copy(NotificationMsgCount *c1,NotificationMsgCount *c2)
+{
+  c1->new_msgs          = c2->new_msgs;
+  c1->unread_msgs       = c2->unread_msgs;
+  c1->unreadmarked_msgs = c2->unreadmarked_msgs;
+  c1->marked_msgs       = c2->marked_msgs;
+  c1->total_msgs        = c2->total_msgs;
+}
+
+gboolean get_flat_gslist_from_nodes_traverse_func(GNode *node, gpointer data)
+{
+  if(node->data) {
+    GSList **list = data;
+    *list = g_slist_prepend(*list, node->data);
+  }
+  return FALSE;
+}
+
+GSList* get_flat_gslist_from_nodes(GNode *node)
+{
+  GSList *retval = NULL;
+
+  g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, get_flat_gslist_from_nodes_traverse_func, &retval);
+  return retval;
+}
+
+void notification_core_get_msg_count_of_foldername(gchar *foldername, NotificationMsgCount *count)
+{
+  GList *list;
+  Folder *walk_folder;
+  Folder *folder = NULL;
+
+  for(list = folder_get_list(); list != NULL; list = list->next) {
+    walk_folder = list->data;
+    if(strcmp2(foldername, walk_folder->name) == 0) {
+      folder = walk_folder;
+      break;
+    }
+  }
+  if(!folder) {
+    debug_print("Notification plugin: Error: Could not find folder %s\n", foldername);
+    return;
+  }
+
+  msg_count_clear(count);
+  notification_core_get_msg_count(get_flat_gslist_from_nodes(folder->node), count);
+}
+
+void notification_core_get_msg_count(GSList *folder_list,
+                                    NotificationMsgCount *count)
+{
+  GSList *walk;
+
+  if(!folder_list)
+    msg_count_copy(count,&msg_count);
+  else {
+    msg_count_clear(count);
+    for(walk = folder_list; walk; walk = walk->next) {
+      gchar *identifier;
+      NotificationMsgCount *item_count;
+      FolderItem *item = (FolderItem*) walk->data;
+      identifier = folder_item_get_identifier(item);
+      if(identifier) {
+       item_count = g_hash_table_lookup(msg_count_hash,identifier);
+       g_free(identifier);
+       if(item_count)
+         msg_count_add(count, item_count);
+      }
+    }
+  }
+}
+
+static void msg_count_hash_update_func(FolderItem *item, gpointer data)
+{
+  gchar *identifier;
+  NotificationMsgCount *count;
+  GHashTable *hash = data;
+
+  if(!notify_include_folder_type(item->folder->klass->type,
+                                item->folder->klass->uistr))
+    return;
+
+  identifier = folder_item_get_identifier(item);
+  if(!identifier)
+    return;
+
+  count = g_hash_table_lookup(hash, identifier);
+
+  if(!count) {
+    count = g_new0(NotificationMsgCount,1);
+    g_hash_table_insert(hash, identifier, count);
+  }
+  else
+    g_free(identifier);
+
+  count->new_msgs          = item->new_msgs;
+  count->unread_msgs       = item->unread_msgs;
+  count->unreadmarked_msgs = item->unreadmarked_msgs;
+  count->marked_msgs       = item->marked_msgs;
+  count->total_msgs        = item->total_msgs;
+}
+
+static void msg_count_update_from_hash(gpointer key, gpointer value,
+                                      gpointer data)
+{
+  NotificationMsgCount *count = value;
+  msg_count_add(&msg_count,count);
+}
+
+
+/* Replacement for the post-filtering hook:
+   Pseudocode by Colin:
+hook on FOLDER_ITEM_UPDATE_HOOKLIST
+ if hook flags & F_ITEM_UPDATE_MSGCOUNT
+  scan mails (folder_item_get_msg_list)
+   if MSG_IS_NEW(msginfo->flags) and not in hashtable
+    notify()
+    add to hashtable
+   procmsg_msg_list_free
+
+hook on MSGINFO_UPDATE_HOOKLIST
+ if hook flags & MSGINFO_UPDATE_FLAGS
+  if !MSG_IS_NEW(msginfo->flags)
+   remove from hashtable, it's now useless
+*/
+
+/* This hash table holds all mails that we already notified about,
+   and that still are marked as "new". The keys are the msgid's,
+   the values are just 1's stored in a pointer. */
+static GHashTable *notified_hash = NULL;
+
+
+/* Remove message from the notified_hash if
+ *  - the message flags changed
+ *  - the message is not new
+ *  - the message is in the hash
+*/
+gboolean notification_notified_hash_msginfo_update(MsgInfoUpdate *msg_update)
+{
+  g_return_val_if_fail(msg_update != NULL, FALSE);
+
+  if((msg_update->flags & MSGINFO_UPDATE_FLAGS) &&
+     !MSG_IS_NEW(msg_update->msginfo->flags)) {
+
+    MsgInfo *msg;
+    gchar *msgid;
+
+    msg = msg_update->msginfo;
+    if(msg->msgid)
+      msgid = msg->msgid;
+    else {
+      debug_print("Notification Plugin: Message has no message ID!\n");
+      msgid = "";
+    }
+
+    g_return_val_if_fail(msg != NULL, FALSE);
+
+    if(g_hash_table_lookup(notified_hash, msgid) != NULL) {
+
+      debug_print("Notification Plugin: Removing message id %s from hash "
+                 "table\n", msgid);
+      g_hash_table_remove(notified_hash, msgid);
+    }
+  }
+  return FALSE;
+}
+
+/* On startup, mark all new mails as already notified
+ * (by including them in the hash) */
+void notification_notified_hash_startup_init(void)
+{
+  GList *folder_list, *walk;
+  Folder *folder;
+
+  if(!notified_hash) {
+    notified_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                         g_free, NULL);
+    debug_print("Notification Plugin: Hash table created\n");
+  }
+
+  folder_list = folder_get_list();
+  for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
+    folder = walk->data;
+
+    g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+                   notification_traverse_hash_startup, NULL);
+  }
+}
+
+static gboolean notification_traverse_hash_startup(GNode *node, gpointer data)
+{
+  GSList *walk;
+  GSList *msg_list;
+  FolderItem *item = (FolderItem*) node->data;
+  gint new_msgs_left;
+
+  if(!(item->new_msgs))
+    return FALSE;
+
+  new_msgs_left = item->new_msgs;
+  msg_list = folder_item_get_msg_list(item);
+
+  for(walk = msg_list; walk; walk = g_slist_next(walk)) {
+    MsgInfo *msg = (MsgInfo*) walk->data;
+    if(MSG_IS_NEW(msg->flags)) {
+      gchar *msgid;
+
+      if(msg->msgid)
+       msgid = msg->msgid;
+      else {
+       debug_print("Notification Plugin: Message has no message ID!\n");
+       msgid = "";
+      }
+
+      /* If the message id is not yet in the hash, add it */
+      g_hash_table_insert(notified_hash, g_strdup(msgid),
+                         GINT_TO_POINTER(1));
+      debug_print("Notification Plugin: Init: Added msg id %s to the hash\n",
+                 msgid);
+      /* Decrement left count and check if we're already done */
+      new_msgs_left--;
+      if(new_msgs_left == 0)
+       break;
+    }
+  }
+  procmsg_msg_list_free(msg_list);
+  return FALSE;
+}
+
+void notification_core_free(void)
+{
+  if(notified_hash) {
+    g_hash_table_destroy(notified_hash);
+    notified_hash = NULL;
+  }
+  if(msg_count_hash) {
+    g_hash_table_destroy(msg_count_hash);
+    msg_count_hash = NULL;
+  }
+  debug_print("Notification Plugin: Freed internal data\n");
+}
+
+void notification_new_unnotified_msgs(FolderItemUpdateData *update_data)
+{
+  GSList *msg_list, *walk;
+
+  g_return_if_fail(notified_hash != NULL);
+
+  msg_list = folder_item_get_msg_list(update_data->item);
+
+  for(walk = msg_list; walk; walk = g_slist_next(walk)) {
+    MsgInfo *msg;
+    msg = (MsgInfo*) walk->data;
+
+    if(MSG_IS_NEW(msg->flags)) {
+      gchar *msgid;
+
+      if(msg->msgid)
+       msgid = msg->msgid;
+      else {
+       debug_print("Notification Plugin: Message has not message ID!\n");
+       msgid = "";
+      }
+
+      debug_print("Notification Plugin: Found msg %s, "
+                 "checking if it is in hash...\n", msgid);
+      /* Check if message is in hash table */
+      if(g_hash_table_lookup(notified_hash, msgid) != NULL)
+       debug_print("yes.\n");
+      else {
+       /* Add to hashtable */
+       g_hash_table_insert(notified_hash, g_strdup(msgid),
+                           GINT_TO_POINTER(1));
+       debug_print("no, added to table.\n");
+
+       /* Do the notification */
+       notification_new_unnotified_do_msg(msg);
+      }
+
+    } /* msg is 'new' */
+  } /* for all messages */
+  procmsg_msg_list_free(msg_list);
+}
+
+#ifdef HAVE_LIBCANBERRA_GTK
+static void canberra_finished_cb(ca_context *c, uint32_t id, int error, void *data)
+{
+  canberra_new_email_is_playing = FALSE;
+}
+#endif
+
+static void notification_new_unnotified_do_msg(MsgInfo *msg)
+{
+#ifdef NOTIFICATION_POPUP
+  notification_popup_msg(msg);
+#endif
+#ifdef NOTIFICATION_COMMAND
+  notification_command_msg(msg);
+#endif
+#ifdef NOTIFICATION_TRAYICON
+  notification_trayicon_msg(msg);
+#endif
+
+#ifdef HAVE_LIBCANBERRA_GTK
+  /* canberra */
+  if(notify_config.canberra_play_sounds && !canberra_new_email_is_playing) {
+    MainWindow *mainwin;
+    ca_proplist *proplist;
+    mainwin = mainwindow_get_mainwindow();
+    ca_proplist_create(&proplist);
+    ca_proplist_sets(proplist,CA_PROP_EVENT_ID ,"message-new-email");
+    canberra_new_email_is_playing = TRUE;
+    ca_context_play_full(ca_gtk_context_get(), 0, proplist, canberra_finished_cb, NULL);
+    ca_proplist_destroy(proplist);
+  }
+#endif
+}
+
+/* If folders is not NULL, then consider only those folder items
+ * If max_msgs is not 0, stop after collecting msg_msgs messages
+ */
+GSList* notification_collect_msgs(gboolean unread_also, GSList *folder_items,
+                                 gint max_msgs)
+{
+  GList *folder_list, *walk;
+  Folder *folder;
+  TraverseCollect collect_data;
+
+  collect_data.unread_also = unread_also;
+  collect_data.collected_msgs = NULL;
+  collect_data.folder_items = folder_items;
+  collect_data.max_msgs = max_msgs;
+  collect_data.num_msgs = 0;
+
+  folder_list = folder_get_list();
+  for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
+    folder = walk->data;
+
+    g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+                   notification_traverse_collect, &collect_data);
+  }
+  return collect_data.collected_msgs;
+}
+
+void notification_collected_msgs_free(GSList *collected_msgs)
+{
+  if(collected_msgs) {
+    GSList *walk;
+    for(walk = collected_msgs; walk != NULL; walk = g_slist_next(walk)) {
+      CollectedMsg *msg = walk->data;
+      if(msg->from)
+                               g_free(msg->from);
+      if(msg->subject)
+                               g_free(msg->subject);
+      if(msg->folderitem_name)
+                               g_free(msg->folderitem_name);
+                       msg->msginfo = NULL;
+      g_free(msg);
+    }
+    g_slist_free(collected_msgs);
+  }
+}
+
+static gboolean notification_traverse_collect(GNode *node, gpointer data)
+{
+  TraverseCollect *cdata = data;
+  FolderItem *item = node->data;
+  gchar *folder_id_cur;
+
+  /* Obey global folder type limitations */
+  if(!notify_include_folder_type(item->folder->klass->type,
+                                item->folder->klass->uistr))
+    return FALSE;
+
+  /* If a folder_items list was given, check it first */
+  if((cdata->folder_items) && (item->path != NULL) &&
+     ((folder_id_cur  = folder_item_get_identifier(item)) != NULL)) {
+    FolderItem *list_item;
+    GSList *walk;
+    gchar *folder_id_list;
+    gboolean eq;
+    gboolean folder_in_list = FALSE;
+
+    for(walk = cdata->folder_items; walk != NULL; walk = g_slist_next(walk)) {
+      list_item = walk->data;
+      folder_id_list = folder_item_get_identifier(list_item);
+      eq = !strcmp2(folder_id_list,folder_id_cur);
+      g_free(folder_id_list);
+      if(eq) {
+       folder_in_list = TRUE;
+       break;
+      }
+    }
+    g_free(folder_id_cur);
+    if(!folder_in_list)
+      return FALSE;
+  }
+
+  if(item->new_msgs || (cdata->unread_also && item->unread_msgs)) {
+    GSList *msg_list = folder_item_get_msg_list(item);
+    GSList *walk;
+    for(walk = msg_list; walk != NULL; walk = g_slist_next(walk)) {
+      MsgInfo *msg_info = walk->data;
+      CollectedMsg *cmsg;
+
+      if((cdata->max_msgs != 0) && (cdata->num_msgs >= cdata->max_msgs))
+       return FALSE;
+
+      if(MSG_IS_NEW(msg_info->flags) ||
+                                (MSG_IS_UNREAD(msg_info->flags) && cdata->unread_also)) {
+
+                               cmsg = g_new(CollectedMsg, 1);
+                               cmsg->from = g_strdup(msg_info->from ? msg_info->from : "");
+                               cmsg->subject = g_strdup(msg_info->subject ? msg_info->subject : "");
+                               if(msg_info->folder && msg_info->folder->name)
+                                       cmsg->folderitem_name = g_strdup(msg_info->folder->path);
+                               else
+                                       cmsg->folderitem_name = g_strdup("");
+
+                               cmsg->msginfo = msg_info;
+
+                               cdata->collected_msgs = g_slist_prepend(cdata->collected_msgs, cmsg);
+                               cdata->num_msgs++;
+      }
+    }
+    procmsg_msg_list_free(msg_list);
+  }
+
+  return FALSE;
+}
+
+gboolean notify_include_folder_type(FolderType ftype, gchar *uistr)
+{
+  gboolean retval;
+
+  retval = FALSE;
+  switch(ftype) {
+  case F_MH:
+  case F_MBOX:
+  case F_MAILDIR:
+  case F_IMAP:
+    if(notify_config.include_mail)
+      retval = TRUE;
+    break;
+  case F_NEWS:
+    if(notify_config.include_news)
+      retval = TRUE;
+    break;
+  case F_UNKNOWN:
+    if(uistr == NULL)
+      retval = FALSE;
+    else if(!strcmp(uistr, "vCalendar")) {
+      if(notify_config.include_calendar)
+       retval = TRUE;
+    }
+    else if(!strcmp(uistr, "RSSyl")) {
+      if(notify_config.include_rss)
+       retval = TRUE;
+    }
+    else
+      debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
+    break;
+  default:
+    debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
+  }
+
+  return retval;
+}
+
+static void fix_folderview_scroll(MainWindow *mainwin)
+{
+       static gboolean fix_done = FALSE;
+
+       if (fix_done)
+               return;
+
+       gtk_widget_queue_resize(mainwin->folderview->ctree);
+
+       fix_done = TRUE;
+}
+
+void notification_show_mainwindow(MainWindow *mainwin)
+{
+      gtk_window_deiconify(GTK_WINDOW(mainwin->window));
+      gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin->window), FALSE);
+      main_window_show(mainwin);
+      gtk_window_present(GTK_WINDOW(mainwin->window));
+      fix_folderview_scroll(mainwin);
+}
+
+#ifdef HAVE_LIBNOTIFY
+#define STR_MAX_LEN 511
+/* Returns a newly allocated string which needs to be freed */
+gchar* notification_libnotify_sanitize_str(gchar *in)
+{
+  gint i_out;
+  gchar tmp_str[STR_MAX_LEN+1];
+
+  if(in == NULL) return NULL;
+
+  i_out = 0;
+  while(*in) {
+    if(*in == '<') {
+      if(i_out+3 >= STR_MAX_LEN) break;
+      memcpy(&(tmp_str[i_out]),"&lt;",4);
+      in++; i_out += 4;
+    }
+    else if(*in == '>') {
+      if(i_out+3 >= STR_MAX_LEN) break;
+      memcpy(&(tmp_str[i_out]),"&gt;",4);
+      in++; i_out += 4;
+    }
+    else if(*in == '&') {
+      if(i_out+4 >= STR_MAX_LEN) break;
+      memcpy(&(tmp_str[i_out]),"&amp;",5);
+      in++; i_out += 5;
+    }
+    else {
+      if(i_out >= STR_MAX_LEN) break;
+      tmp_str[i_out++] = *in++;
+    }
+  }
+  tmp_str[i_out] = '\0';
+  return strdup(tmp_str);
+}
+
+gchar* notification_validate_utf8_str(gchar *text)
+{
+  gchar *utf8_str = NULL;
+
+  if(!g_utf8_validate(text, -1, NULL)) {
+    debug_print("Notification plugin: String is not valid utf8, "
+               "trying to fix it...\n");
+    /* fix it */
+    utf8_str = conv_codeset_strdup(text,
+                                  conv_get_locale_charset_str_no_utf8(),
+                                  CS_INTERNAL);
+    /* check if the fix worked */
+    if(utf8_str == NULL || !g_utf8_validate(utf8_str, -1, NULL)) {
+      debug_print("Notification plugin: String is still not valid utf8, "
+                 "sanitizing...\n");
+      utf8_str = g_malloc(strlen(text)*2+1);
+      conv_localetodisp(utf8_str, strlen(text)*2+1, text);
+    }
+  }
+  else {
+    debug_print("Notification plugin: String is valid utf8\n");
+    utf8_str = g_strdup(text);
+  }
+  return utf8_str;
+}
+#endif
diff --git a/src/plugins/notification/notification_core.h b/src/plugins/notification/notification_core.h
new file mode 100644 (file)
index 0000000..b1ed955
--- /dev/null
@@ -0,0 +1,79 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NOTIFICATION_CORE_H
+#define NOTIFICATION_CORE_H NOTIFICATION_CORE_H
+
+#include "mainwindow.h"
+#include "folder.h"
+#include "procmsg.h"
+
+typedef struct {
+  gchar *from;
+  gchar *subject;
+  FolderItem *folder_item;
+  gchar *folderitem_name;
+       MsgInfo *msginfo;
+} CollectedMsg;
+
+typedef enum {
+  F_TYPE_MAIL=0,
+  F_TYPE_NEWS,
+  F_TYPE_CALENDAR,
+  F_TYPE_RSS,
+  F_TYPE_LAST
+} NotificationFolderType;
+
+typedef struct 
+{
+  guint new_msgs;
+  guint unread_msgs;
+  guint unreadmarked_msgs;
+  guint marked_msgs;
+  guint total_msgs;
+} NotificationMsgCount;
+
+/* Collect new and possibly unread messages in all folders */
+GSList*  notification_collect_msgs(gboolean, GSList*, gint);
+void     notification_collected_msgs_free(GSList*);
+
+void     notification_core_global_includes_changed(void);
+void     notification_core_free(void);
+void     notification_update_msg_counts(FolderItem*);
+void     notification_core_get_msg_count(GSList*,NotificationMsgCount*);
+void     notification_core_get_msg_count_of_foldername(gchar*, NotificationMsgCount*);
+void     notification_new_unnotified_msgs(FolderItemUpdateData*);
+gboolean notification_notified_hash_msginfo_update(MsgInfoUpdate*);
+void     notification_notified_hash_startup_init(void);
+
+void     notification_show_mainwindow(MainWindow *mainwin);
+/* folder type specific settings */
+gboolean notify_include_folder_type(FolderType, gchar*);
+
+void notification_toggle_hide_show_window(void);
+
+#ifdef HAVE_LIBNOTIFY
+/* Sanitize a string to use with libnotify. Returns a freshly
+ * allocated string that must be freed by the user. */
+gchar* notification_libnotify_sanitize_str(gchar*);
+/* Returns a freshly allocated copy of the input string, which
+ * is guaranteed to be valid UTF8. The returned string has to
+ * be freed. */
+gchar* notification_validate_utf8_str(gchar*);
+#endif
+
+#endif /* NOTIFICATION_CORE_H */
diff --git a/src/plugins/notification/notification_foldercheck.c b/src/plugins/notification/notification_foldercheck.c
new file mode 100644 (file)
index 0000000..a401138
--- /dev/null
@@ -0,0 +1,1002 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This code is based on foldersel.c in Claws Mail.
+ * Some functions are only slightly modified, almost 1:1 copies from there. */
+
+#include "pluginconfig.h"
+
+/* Basic definitions first */
+#include "common/defs.h"
+
+/* System includes */
+#include <string.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+/* Claws Mail includes */
+#include "manage_window.h"
+#include "folder.h"
+#include "stock_pixmap.h"
+#include "gtk/gtkutils.h"
+#include "common/utils.h"
+#include "common/prefs.h"
+#include "common/xml.h"
+#include "common/hooks.h"
+#include "prefs_common.h"
+
+/* local includes */
+#include "notification_foldercheck.h"
+
+/* enums and structures */
+enum {
+  FOLDERCHECK_FOLDERNAME,
+  FOLDERCHECK_FOLDERITEM,
+  FOLDERCHECK_PIXBUF,
+  FOLDERCHECK_PIXBUF_OPEN,
+  FOLDERCHECK_CHECK,
+  N_FOLDERCHECK_COLUMNS
+};
+
+typedef struct {
+  /* Data */
+  gchar        *name;
+  GSList       *list;
+  GtkTreeStore *tree_store;
+  /* Dialog box*/
+  GtkWidget *window;
+  GtkWidget *treeview;
+  gboolean cancelled;
+  gboolean finished;
+  gboolean recursive;
+} SpecificFolderArrayEntry;
+
+/* variables with file scope */ 
+static GdkPixbuf *folder_pixbuf;
+static GdkPixbuf *folderopen_pixbuf;
+static GdkPixbuf *foldernoselect_pixbuf;
+
+static GArray *specific_folder_array;
+static guint   specific_folder_array_size;
+
+static guint hook_folder_update;
+
+
+/* defines */
+#define FOLDERCHECK_ARRAY "notification_foldercheck.xml"
+#define foldercheck_get_entry_from_id(id) \
+  ((id) < specific_folder_array_size) ? \
+  g_array_index(specific_folder_array,SpecificFolderArrayEntry*,(id)) : NULL
+
+/* function prototypes */
+static void folder_checked(guint);
+static void foldercheck_create_window(SpecificFolderArrayEntry*);
+static void foldercheck_destroy_window(SpecificFolderArrayEntry*);
+static gint foldercheck_folder_name_compare(GtkTreeModel*, GtkTreeIter*,
+                                           GtkTreeIter*, gpointer);
+static gboolean foldercheck_selected(GtkTreeSelection*,
+                                    GtkTreeModel*, GtkTreePath*,
+                                    gboolean, gpointer);
+
+static gint delete_event(GtkWidget*, GdkEventAny*, gpointer);
+static void foldercheck_ok(GtkButton*, gpointer);
+static void foldercheck_cancel(GtkButton*, gpointer);
+static void foldercheck_set_tree(SpecificFolderArrayEntry*);
+static void foldercheck_insert_gnode_in_store(GtkTreeStore*, GNode*,
+                                             GtkTreeIter*);
+static void foldercheck_append_item(GtkTreeStore*, FolderItem*,
+                                   GtkTreeIter*, GtkTreeIter*);
+static void foldercheck_recursive_cb(GtkToggleButton*, gpointer);
+static void folder_toggle_cb(GtkCellRendererToggle*, gchar*, gpointer);
+static void folder_toggle_recurse_tree(GtkTreeStore*, GtkTreeIter*, gint,
+                                      gboolean);
+static gboolean foldercheck_foreach_check(GtkTreeModel*, GtkTreePath*,
+                                         GtkTreeIter*, gpointer);
+static gboolean foldercheck_foreach_update_to_list(GtkTreeModel*, GtkTreePath*,
+                                                  GtkTreeIter*, gpointer);
+static gchar *foldercheck_get_array_path(void);
+static gboolean my_folder_update_hook(gpointer, gpointer);
+static gboolean key_pressed(GtkWidget*, GdkEventKey*,gpointer);
+
+
+/* Creates an entry in the specific_folder_array, and fills it with a new
+ * SpecificFolderArrayEntry*. If specific_folder_array already has an entry
+ * with the same name, return its ID. (The ID is the index in the array.) */
+guint notification_register_folder_specific_list(gchar *node_name)
+{
+  SpecificFolderArrayEntry *entry;
+  gint ii = 0;
+
+  /* If array does not yet exist, create it. */
+  if(!specific_folder_array) {
+    specific_folder_array = g_array_new(FALSE, FALSE,
+                                       sizeof(SpecificFolderArrayEntry*));
+    specific_folder_array_size = 0;
+
+    /* Register hook for folder update */
+    /* "The hook is registered" is bound to "the array is allocated" */
+    hook_folder_update = hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
+                                            my_folder_update_hook, NULL);
+    if(hook_folder_update == (guint) -1) {
+      debug_print("Warning: Failed to register hook to folder update "
+                 "hooklist. "
+                 "Strange things can occur when deleting folders.\n");
+    }
+  }
+
+  /* Check if we already have such a name. If so, return its id. */
+  while(ii < specific_folder_array_size) {
+    entry = g_array_index(specific_folder_array,SpecificFolderArrayEntry*,ii);
+    if(entry) {
+      if(!strcmp2(entry->name,node_name))
+       return ii;
+    }
+    ii++;
+  }
+
+  /* Create an entry with the corresponding node name. */
+  entry = g_new(SpecificFolderArrayEntry, 1);
+  entry->name = g_strdup(node_name);
+  entry->list = NULL;
+  entry->window = NULL;
+  entry->treeview = NULL;
+  entry->cancelled = FALSE;
+  entry->finished  = FALSE;
+  entry->recursive = FALSE;
+  entry->tree_store = gtk_tree_store_new(N_FOLDERCHECK_COLUMNS,
+                                        G_TYPE_STRING,
+                                        G_TYPE_POINTER,
+                                        GDK_TYPE_PIXBUF,
+                                        GDK_TYPE_PIXBUF,
+                                        G_TYPE_BOOLEAN);
+  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(entry->tree_store),
+                                 FOLDERCHECK_FOLDERNAME,
+                                 foldercheck_folder_name_compare,
+                                 NULL, NULL);
+  specific_folder_array = g_array_append_val(specific_folder_array, entry);
+  return specific_folder_array_size++;
+}
+
+/* This function is called in plugin_done. It frees the whole
+ * folder_specific_array with all its entries. */
+void notification_free_folder_specific_array(void)
+{
+  guint ii;
+  SpecificFolderArrayEntry *entry;
+
+  for(ii = 0; ii < specific_folder_array_size; ii++) {
+    entry = g_array_index(specific_folder_array,SpecificFolderArrayEntry*,ii);
+    if(entry) {
+      g_free(entry->name);
+      if(entry->list)
+       g_slist_free(entry->list);
+      if(entry->tree_store)
+       g_object_unref(G_OBJECT(entry->tree_store));
+      g_free(entry);
+    }
+  }
+  if(specific_folder_array) {
+    /* Free array */
+    g_array_free(specific_folder_array, TRUE);
+
+    /* Unregister hook */
+    hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_folder_update);
+  }
+  specific_folder_array = NULL;
+  specific_folder_array_size = 0;
+}
+
+/* Returns the list of the entry with the corresponding ID, or NULL if
+ * no such element exists. */
+GSList* notification_foldercheck_get_list(guint id)
+{
+  SpecificFolderArrayEntry *entry;
+  
+  entry = foldercheck_get_entry_from_id(id);
+  if(entry) {
+    return entry->list;
+  }
+  else
+    return NULL;
+}
+
+/* Save selections in a common xml-file. Called when unloading the plugin.
+ * This is analog to folder.h::folder_write_list. */
+void notification_foldercheck_write_array(void)
+{
+  gchar *path;
+  XMLTag *tag;
+  XMLNode *xmlnode;
+  GNode *rootnode;
+  gint ii;
+  PrefFile *pfile;
+
+  /* Do nothing if foldercheck is not in use */
+  if(specific_folder_array_size == 0)
+    return;
+
+  path = foldercheck_get_array_path();
+  if((pfile = prefs_write_open(path)) == NULL) {
+    debug_print("Notification Plugin Error: Cannot open "
+               "file " FOLDERCHECK_ARRAY " for writing\n");
+    return;
+  }
+
+  /* XML declarations */
+  xml_file_put_xml_decl(pfile->fp);
+
+  /* Build up XML tree */
+  
+  /* root node */
+  tag = xml_tag_new("foldercheckarray");
+  xmlnode = xml_node_new(tag, NULL);
+  rootnode = g_node_new(xmlnode);
+
+  /* branch nodes */
+  for(ii = 0; ii < specific_folder_array_size; ii++) {
+    GNode *branchnode;
+    GSList *walk;
+    SpecificFolderArrayEntry *entry;
+  
+    entry = foldercheck_get_entry_from_id(ii);
+    
+    tag = xml_tag_new("branch");
+    xml_tag_add_attr(tag, xml_attr_new("name",entry->name));
+    xmlnode = xml_node_new(tag, NULL);
+    branchnode = g_node_new(xmlnode);
+    g_node_append(rootnode, branchnode);
+
+    /* Write out the list as leaf nodes */
+    for(walk = entry->list; walk != NULL; walk = g_slist_next(walk)) {
+      gchar *identifier;
+      GNode *node;
+      FolderItem *item = (FolderItem*) walk->data;
+
+      identifier = folder_item_get_identifier(item);
+
+      tag = xml_tag_new("folderitem");
+      xml_tag_add_attr(tag, xml_attr_new("identifier", identifier));
+      g_free(identifier);
+      xmlnode = xml_node_new(tag, NULL);
+      node = g_node_new(xmlnode);
+      g_node_append(branchnode, node);
+    } /* for all list elements in branch node */
+
+  } /* for all branch nodes */
+
+  /* Actual writing and cleanup */
+  xml_write_tree(rootnode, pfile->fp);
+
+  if(prefs_file_close(pfile) < 0) {
+    debug_print("Notification Plugin Error: Failed to write "
+               "file " FOLDERCHECK_ARRAY "\n");
+  }
+
+  /* Free XML tree */
+  xml_free_tree(rootnode);
+}
+
+/* Read selections from a common xml-file. Called when loading the plugin.
+ * Returns TRUE if data has been read, FALSE if no data is available
+ * or an error occured.
+ * This is analog to folder.h::folder_read_list. */
+gboolean notification_foldercheck_read_array(void)
+{
+  gchar *path;
+  GNode *rootnode, *node, *branchnode;
+  XMLNode *xmlnode;
+  gboolean success = FALSE;
+
+  path = foldercheck_get_array_path();
+  if(!is_file_exist(path)) {
+    path = NULL;
+    return FALSE;
+  }
+
+  /* We don't do merging, so if the file existed, clear what we
+     have stored in memory right now.. */
+  notification_free_folder_specific_array();
+
+  /* .. and evaluate the file */
+  rootnode = xml_parse_file(path);
+  path = NULL;
+  if(!rootnode)
+    return FALSE;
+
+  xmlnode = rootnode->data;
+
+  /* Check that root entry is "foldercheckarray" */
+  if(strcmp2(xmlnode->tag->tag, "foldercheckarray") != 0) {
+    g_warning("wrong foldercheck array file\n");
+    xml_free_tree(rootnode);
+    return FALSE;
+  }
+
+  /* Process branch entries */
+  for(branchnode = rootnode->children; branchnode != NULL;
+      branchnode = branchnode->next) {
+    GList *list;
+    guint id;
+    SpecificFolderArrayEntry *entry = NULL;
+
+    xmlnode = branchnode->data;
+    if(strcmp2(xmlnode->tag->tag, "branch") != 0) {
+      g_warning("tag name != \"branch\"\n");
+      return FALSE;
+    }
+
+    /* Attributes of the branch nodes */
+    list = xmlnode->tag->attr;
+    for(; list != NULL; list = list->next) {
+      XMLAttr *attr = list->data;
+
+      if(attr && attr->name && attr->value &&  !strcmp2(attr->name, "name")) {
+       id = notification_register_folder_specific_list(attr->value);
+       entry = foldercheck_get_entry_from_id(id);
+       /* We have found something */
+       success = TRUE;
+       break;
+      }
+    }
+    if((list == NULL) || (entry == NULL)) {
+      g_warning("Did not find attribute \"name\" in tag \"branch\"\n");
+      continue; /* with next branch */
+    }
+
+    /* Now descent into the children of the brach, which are the folderitems */
+    for(node = branchnode->children; node != NULL; node = node->next) {
+      FolderItem *item = NULL;
+
+      /* These should all be leaves. */
+      if(!G_NODE_IS_LEAF(node))
+       g_warning("Subnodes in \"branch\" nodes should all be leaves. "
+                 "Ignoring deeper subnodes..\n");
+
+      /* Check if tag is "folderitem" */
+      xmlnode = node->data;
+      if(strcmp2(xmlnode->tag->tag, "folderitem") != 0) {
+       g_warning("tag name != \"folderitem\"\n");
+       continue; /* to next node in branch */
+      }
+
+      /* Attributes of the leaf nodes */
+      list = xmlnode->tag->attr;
+      for(; list != NULL; list = list->next) {
+       XMLAttr *attr = list->data;
+
+       if(attr && attr->name && attr->value &&
+          !strcmp2(attr->name, "identifier")) {
+         item = folder_find_item_from_identifier(attr->value);
+         break;
+       }
+      }
+      if((list == NULL) || (item == NULL)) {
+       g_warning("Did not find attribute \"identifier\" in tag "
+                 "\"folderitem\"\n");
+       continue; /* with next leaf node */
+      }
+      
+      /* Store all FolderItems in the list */
+      /* We started with a cleared array, so we don't need to check if
+        it's already in there. */
+      entry->list = g_slist_prepend(entry->list, item);
+
+    } /* for all subnodes in branch */
+
+  } /* for all branches */
+  return success;
+}
+
+/* Stolen from folder.c. Return value should NOT be freed. */
+static gchar *foldercheck_get_array_path(void)
+{
+  static gchar *filename = NULL;
+
+  if(!filename)
+    filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+                          FOLDERCHECK_ARRAY, NULL);
+  return filename;
+}
+
+/* Callback for selecting folders. If no selection dialog exists yet, create
+ * one and initialize the selection. The recurse through the whole model, and
+ * add all selected items to the list. */
+static void folder_checked(guint id)
+{
+  SpecificFolderArrayEntry *entry;
+  GSList *checked_list = NULL;
+
+  entry = foldercheck_get_entry_from_id(id);
+
+  /* Create window */
+  foldercheck_create_window(entry);
+  gtk_widget_show(entry->window);
+  manage_window_set_transient(GTK_WINDOW(entry->window));
+
+  entry->cancelled = entry->finished = FALSE;
+  while(entry->finished == FALSE)
+    gtk_main_iteration();
+  
+  foldercheck_destroy_window(entry);
+
+  if(!entry->cancelled) {
+    /* recurse through the whole model, add all selected items to the list */
+    gtk_tree_model_foreach(GTK_TREE_MODEL(entry->tree_store),
+                          foldercheck_foreach_check, &checked_list);
+
+    if(entry->list) {
+      g_slist_free(entry->list);
+      entry->list = NULL;
+    }
+    entry->list = g_slist_copy(checked_list);
+    g_slist_free(checked_list);
+  }
+
+  gtk_tree_store_clear(entry->tree_store);
+
+  entry->cancelled = FALSE;
+  entry->finished  = FALSE;
+}
+
+/* Create the window for selecting folders with checkboxes */
+static void foldercheck_create_window(SpecificFolderArrayEntry *entry)
+{
+  GtkWidget *vbox;
+  GtkWidget *scrolledwin;
+  GtkWidget *confirm_area;
+  GtkWidget *checkbox;
+  GtkWidget *cancel_button;
+  GtkWidget *ok_button;
+  GtkTreeSelection *selection;
+  GtkTreeViewColumn *column;
+  GtkCellRenderer *renderer;
+  static GdkGeometry geometry;
+
+  /* Create window */
+  entry->window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "notification_foldercheck");
+  gtk_window_set_title(GTK_WINDOW(entry->window), _("Select folder(s)"));
+  gtk_container_set_border_width(GTK_CONTAINER(entry->window), 4);
+  gtk_window_set_position(GTK_WINDOW(entry->window), GTK_WIN_POS_CENTER);
+  gtk_window_set_modal(GTK_WINDOW(entry->window), TRUE);
+  gtk_window_set_resizable(GTK_WINDOW(entry->window), TRUE);
+  gtk_window_set_wmclass
+    (GTK_WINDOW(entry->window), "folder_selection", "Claws Mail");  
+  g_signal_connect(G_OBJECT(entry->window), "delete_event",
+                  G_CALLBACK(delete_event), entry);
+  g_signal_connect(G_OBJECT(entry->window), "key_press_event",
+      G_CALLBACK(key_pressed), entry);
+  MANAGE_WINDOW_SIGNALS_CONNECT(entry->window);
+
+  /* vbox */
+  vbox = gtk_vbox_new(FALSE, 4);
+  gtk_container_add(GTK_CONTAINER(entry->window), vbox);
+
+  /* scrolled window */
+  scrolledwin = gtk_scrolled_window_new(NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
+                                GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
+                                     GTK_SHADOW_IN);
+  gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
+
+  /* pixbufs */
+  if(!folder_pixbuf)
+    stock_pixbuf_gdk(scrolledwin, STOCK_PIXMAP_DIR_CLOSE,
+                    &folder_pixbuf);
+  if(!folderopen_pixbuf)
+    stock_pixbuf_gdk(scrolledwin, STOCK_PIXMAP_DIR_OPEN,
+                    &folderopen_pixbuf);
+  if(!foldernoselect_pixbuf)
+    stock_pixbuf_gdk(scrolledwin, STOCK_PIXMAP_DIR_NOSELECT,
+                    &foldernoselect_pixbuf);
+
+  /* Tree store */
+  foldercheck_set_tree(entry);
+  gtk_tree_model_foreach(GTK_TREE_MODEL(entry->tree_store),
+                        foldercheck_foreach_update_to_list, entry);
+
+
+  /* tree view */
+  entry->treeview =
+    gtk_tree_view_new_with_model(GTK_TREE_MODEL(entry->tree_store));
+  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(entry->treeview), FALSE);
+  gtk_tree_view_set_search_column(GTK_TREE_VIEW(entry->treeview),
+                                 FOLDERCHECK_FOLDERNAME);
+  gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(entry->treeview),
+                                 prefs_common_get_prefs()->use_stripes_everywhere);
+  gtk_tree_view_set_enable_tree_lines(GTK_TREE_VIEW(entry->treeview), FALSE);
+
+  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(entry->treeview));
+  gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
+  gtk_tree_selection_set_select_function(selection, foldercheck_selected,
+                                        NULL, NULL);
+
+  gtk_container_add(GTK_CONTAINER(scrolledwin), entry->treeview);
+
+  /* --- column 1 --- */
+  column = gtk_tree_view_column_new();
+  gtk_tree_view_column_set_title(column, "sel");
+  gtk_tree_view_column_set_spacing(column, 2);
+  
+  /* checkbox */
+  renderer = gtk_cell_renderer_toggle_new();
+  g_object_set(renderer, "xalign", 0.0, NULL);
+  gtk_tree_view_column_pack_start(column, renderer, TRUE);
+  g_signal_connect(renderer, "toggled", G_CALLBACK(folder_toggle_cb),entry);
+  gtk_tree_view_column_set_attributes(column, renderer,
+                                     "active", FOLDERCHECK_CHECK,NULL);
+
+  gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+  gtk_tree_view_append_column(GTK_TREE_VIEW(entry->treeview), column);
+
+  /* --- column 2 --- */
+  column = gtk_tree_view_column_new();
+  gtk_tree_view_column_set_title(column, "Folder");
+  gtk_tree_view_column_set_spacing(column, 2);
+
+  /* pixbuf */
+  renderer = gtk_cell_renderer_pixbuf_new();
+  gtk_tree_view_column_pack_start(column, renderer, FALSE);
+  gtk_tree_view_column_set_attributes
+    (column, renderer,
+     "pixbuf", FOLDERCHECK_PIXBUF,
+     "pixbuf-expander-open", FOLDERCHECK_PIXBUF_OPEN,
+     "pixbuf-expander-closed", FOLDERCHECK_PIXBUF,
+     NULL);
+
+  /* text */
+  renderer = gtk_cell_renderer_text_new();
+  gtk_tree_view_column_pack_start(column, renderer, TRUE);
+  gtk_tree_view_column_set_attributes(column, renderer,
+                                     "text", FOLDERCHECK_FOLDERNAME,
+                                     NULL);
+
+  gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+  gtk_tree_view_append_column(GTK_TREE_VIEW(entry->treeview), column);
+
+  /* recursive */
+  checkbox = gtk_check_button_new_with_label( _("select recursively"));
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox), FALSE);
+  g_signal_connect(G_OBJECT(checkbox), "toggled",
+                  G_CALLBACK(foldercheck_recursive_cb), entry);
+  gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 10);
+
+  gtkut_stock_button_set_create(&confirm_area,
+                               &cancel_button, GTK_STOCK_CANCEL,
+                               &ok_button,     GTK_STOCK_OK,
+                               NULL,           NULL);
+  gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
+  gtk_widget_grab_default(ok_button);
+
+  g_signal_connect(G_OBJECT(ok_button), "clicked",
+                  G_CALLBACK(foldercheck_ok), entry);
+  g_signal_connect(G_OBJECT(cancel_button), "clicked",
+                  G_CALLBACK(foldercheck_cancel), entry);
+
+  if(!geometry.min_height) {
+    geometry.min_width = 360;
+    geometry.min_height = 360;
+  }
+
+  gtk_window_set_geometry_hints(GTK_WINDOW(entry->window), NULL, &geometry,
+                               GDK_HINT_MIN_SIZE);
+
+  gtk_tree_view_expand_all(GTK_TREE_VIEW(entry->treeview));
+
+  gtk_widget_show_all(vbox);
+}
+
+static void foldercheck_destroy_window(SpecificFolderArrayEntry *entry)
+{
+  gtk_widget_destroy(entry->window);
+  entry->window = NULL;
+  entry->treeview = NULL;
+  entry->recursive = FALSE;
+}
+
+/* Handler for the delete event of the windows for selecting folders */
+static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data)
+{
+  foldercheck_cancel(NULL, data);
+  return TRUE;
+}
+
+/* sortable_set_sort_func */
+static gint foldercheck_folder_name_compare(GtkTreeModel *model,
+                                           GtkTreeIter *a, GtkTreeIter *b,
+                                           gpointer context)
+{
+  gchar *str_a = NULL, *str_b = NULL;
+  gint val = 0;
+  FolderItem *item_a = NULL, *item_b = NULL;
+  GtkTreeIter parent;
+
+  gtk_tree_model_get(model, a, FOLDERCHECK_FOLDERITEM, &item_a, -1);
+  gtk_tree_model_get(model, b, FOLDERCHECK_FOLDERITEM, &item_b, -1);
+
+  /* no sort for root folder */
+  if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(model), &parent, a))
+    return 0;
+
+  /* if both a and b are special folders, sort them according to
+   * their types (which is in-order). Note that this assumes that
+   * there are no multiple folders of a special type. */
+  if (item_a->stype != F_NORMAL && item_b->stype != F_NORMAL)
+    return item_a->stype - item_b->stype;
+
+  /* if b is normal folder, and a is not, b is smaller (ends up
+   * lower in the list) */
+  if (item_a->stype != F_NORMAL && item_b->stype == F_NORMAL)
+    return item_b->stype - item_a->stype;
+
+  /* if b is special folder, and a is not, b is larger (ends up
+   * higher in the list) */
+  if (item_a->stype == F_NORMAL && item_b->stype != F_NORMAL)
+    return item_b->stype - item_a->stype;
+
+  /* XXX g_utf8_collate_key() comparisons may speed things
+   * up when having large lists of folders */
+  gtk_tree_model_get(model, a, FOLDERCHECK_FOLDERNAME, &str_a, -1);
+  gtk_tree_model_get(model, b, FOLDERCHECK_FOLDERNAME, &str_b, -1);
+
+  /* otherwise just compare the folder names */
+  val = g_utf8_collate(str_a, str_b);
+  
+  g_free(str_a);
+  g_free(str_b);
+
+  return val;
+}
+
+/* select_function of the gtk tree selection */
+static gboolean foldercheck_selected(GtkTreeSelection *selection,
+                                    GtkTreeModel *model, GtkTreePath *path,
+                                    gboolean currently_selected,gpointer data)
+{
+  GtkTreeIter iter;
+  FolderItem *item = NULL;
+
+  if (currently_selected)
+    return TRUE;
+
+  if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
+    return TRUE;
+
+  gtk_tree_model_get(model, &iter, FOLDERCHECK_FOLDERITEM, &item, -1);
+
+  return TRUE;
+}
+
+/* Callback for the OK-button of the folderselection dialog */
+static void foldercheck_ok(GtkButton *button, gpointer data)
+{
+  SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
+
+  entry->finished = TRUE;
+}
+
+/* Callback for the Cancel-button of the folderselection dialog. Gets also
+ * called on a delete-event of the folderselection window. */
+static void foldercheck_cancel(GtkButton *button, gpointer data)
+{
+  SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
+
+  entry->cancelled = TRUE;
+  entry->finished  = TRUE;
+}
+
+/* Set tree of the tree-store. This includes getting the folder tree and
+ * storing it */
+static void foldercheck_set_tree(SpecificFolderArrayEntry *entry)
+{
+  Folder *folder;
+  GList *list;
+
+  for(list = folder_get_list(); list != NULL; list = list->next) {
+    folder = FOLDER(list->data);
+
+    if(folder == NULL) {
+      debug_print("Notification plugin::foldercheck_set_tree(): Found a NULL folder.\n");
+      continue;
+    }
+
+    /* Only regard built-in folders, because folders from plugins (such as RSS, calendar,
+     * or plugin-provided mailbox storage systems like Maildir or MBox) may vanish
+     * without letting us know. */
+    switch(folder->klass->type) {
+    case F_MH:
+    case F_IMAP:
+    case F_NEWS:
+      foldercheck_insert_gnode_in_store(entry->tree_store, folder->node, NULL);
+      break;
+    default:
+      break;
+    }
+  }
+
+  gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(entry->tree_store),
+                                      FOLDERCHECK_FOLDERNAME,
+                                      GTK_SORT_ASCENDING);
+
+  if(GTK_IS_TREE_VIEW(entry->treeview))
+    gtk_tree_view_expand_all(GTK_TREE_VIEW(entry->treeview));
+}
+
+/* Helper function for foldercheck_set_tree */
+static void foldercheck_insert_gnode_in_store(GtkTreeStore *store, GNode *node,
+                                             GtkTreeIter *parent)
+{
+  FolderItem *item;
+  GtkTreeIter child;
+  GNode *iter;
+
+  g_return_if_fail(node != NULL);
+  g_return_if_fail(node->data != NULL);
+  g_return_if_fail(store != NULL);
+
+  item = FOLDER_ITEM(node->data);
+  foldercheck_append_item(store, item, &child, parent);
+
+  /* insert its children (this node as parent) */
+  for(iter = node->children; iter != NULL; iter = iter->next)
+    foldercheck_insert_gnode_in_store(store, iter, &child);
+}
+
+/* Helper function for foldercheck_insert_gnode_in_store */
+static void foldercheck_append_item(GtkTreeStore *store, FolderItem *item,
+                                   GtkTreeIter *iter, GtkTreeIter *parent)
+{
+  gchar *name, *tmpname;
+  GdkPixbuf *pixbuf, *pixbuf_open;
+  gboolean use_color;
+  PangoWeight weight = PANGO_WEIGHT_NORMAL;
+  GdkColor *foreground = NULL;
+  static GdkColor color_noselect = {0, COLOR_DIM, COLOR_DIM, COLOR_DIM};
+
+  name = tmpname = folder_item_get_name(item);
+
+  if (item->stype != F_NORMAL && FOLDER_IS_LOCAL(item->folder)) {
+    switch (item->stype) {
+    case F_INBOX:
+      if (!strcmp2(item->name, INBOX_DIR))
+       name = "Inbox";
+      break;
+    case F_OUTBOX:
+      if (!strcmp2(item->name, OUTBOX_DIR))
+       name = "Sent";
+      break;
+    case F_QUEUE:
+      if (!strcmp2(item->name, QUEUE_DIR))
+       name = "Queue";
+      break;
+    case F_TRASH:
+      if (!strcmp2(item->name, TRASH_DIR))
+       name = "Trash";
+      break;
+    case F_DRAFT:
+      if (!strcmp2(item->name, DRAFT_DIR))
+       name = "Drafts";
+      break;
+    default:
+      break;
+    }
+  }
+
+  if (folder_has_parent_of_type(item, F_QUEUE) && item->total_msgs > 0) {
+    name = g_strdup_printf("%s (%d)", name, item->total_msgs);
+  } else if (item->unread_msgs > 0) {
+    name = g_strdup_printf("%s (%d)", name, item->unread_msgs);
+  } else
+    name = g_strdup(name);
+  
+  pixbuf = item->no_select ? foldernoselect_pixbuf : folder_pixbuf;
+  pixbuf_open =
+    item->no_select ? foldernoselect_pixbuf : folderopen_pixbuf;
+
+  if (folder_has_parent_of_type(item, F_DRAFT) ||
+      folder_has_parent_of_type(item, F_OUTBOX) ||
+      folder_has_parent_of_type(item, F_TRASH)) {
+    use_color = FALSE;
+  } else if (folder_has_parent_of_type(item, F_QUEUE)) {
+    use_color = (item->total_msgs > 0);
+    if (item->total_msgs > 0)
+      weight = PANGO_WEIGHT_BOLD;
+  } else {
+    if (item->unread_msgs > 0)
+      weight = PANGO_WEIGHT_BOLD;
+    use_color = (item->new_msgs > 0);
+  }
+  
+  if (item->no_select)
+    foreground = &color_noselect;
+  
+  /* insert this node */
+  gtk_tree_store_append(store, iter, parent);
+  gtk_tree_store_set(store, iter,
+                    FOLDERCHECK_FOLDERNAME, name,
+                    FOLDERCHECK_FOLDERITEM, item,
+                    FOLDERCHECK_PIXBUF, pixbuf,
+                    FOLDERCHECK_PIXBUF_OPEN, pixbuf_open,
+                    -1);
+
+  g_free(tmpname);
+}
+
+/* Callback of the recursive-checkbox */
+static void foldercheck_recursive_cb(GtkToggleButton *button, gpointer data)
+{
+  SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
+
+  entry->recursive = gtk_toggle_button_get_active(button);
+}
+
+/* Callback of the checkboxes corresponding to the folders. Obeys
+ * the "recursive" selection. */
+static void folder_toggle_cb(GtkCellRendererToggle *cell_renderer,
+                            gchar *path_str, gpointer data)
+{
+  gboolean toggle_item;
+  GtkTreeIter iter;
+  SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
+  GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
+
+  gtk_tree_model_get_iter(GTK_TREE_MODEL(entry->tree_store), &iter, path);
+  gtk_tree_path_free(path);
+  gtk_tree_model_get(GTK_TREE_MODEL(entry->tree_store), &iter,
+                    FOLDERCHECK_CHECK, &toggle_item, -1);
+  toggle_item = !toggle_item;
+
+  if(!entry->recursive)
+    gtk_tree_store_set(entry->tree_store, &iter,
+                      FOLDERCHECK_CHECK, toggle_item, -1);
+  else {
+    GtkTreeIter child;
+    gtk_tree_store_set(entry->tree_store, &iter,
+                      FOLDERCHECK_CHECK, toggle_item, -1);
+    if(gtk_tree_model_iter_children(GTK_TREE_MODEL(entry->tree_store),
+                                   &child, &iter))
+      folder_toggle_recurse_tree(entry->tree_store,&child,
+                                FOLDERCHECK_CHECK,toggle_item);
+  }
+
+  while(gtk_events_pending())
+    gtk_main_iteration();
+}
+
+/* Helper function for folder_toggle_cb */
+/* This function calls itself recurively */
+static void folder_toggle_recurse_tree(GtkTreeStore *tree_store,
+                                      GtkTreeIter *iterp, gint column,
+                                      gboolean toggle_item)
+{
+  GtkTreeIter iter = *iterp;
+  GtkTreeIter next;
+
+  /* set the value of this iter */
+  gtk_tree_store_set(tree_store, &iter, column, toggle_item, -1);
+
+  /* do the same for the first child */
+  if(gtk_tree_model_iter_children(GTK_TREE_MODEL(tree_store),&next, &iter))
+    folder_toggle_recurse_tree(tree_store,&next,
+                              FOLDERCHECK_CHECK, toggle_item);
+
+  /* do the same for the next sibling */
+  if(gtk_tree_model_iter_next(GTK_TREE_MODEL(tree_store), &iter))
+    folder_toggle_recurse_tree(tree_store, &iter,
+                              FOLDERCHECK_CHECK, toggle_item);
+}
+
+/* Helper function to be used with a foreach statement of the model. Checks
+ * if a node is checked, and adds it to a list if it is. data us a (GSList**)
+ * where the result it to be stored */
+static gboolean foldercheck_foreach_check(GtkTreeModel *model,
+                                         GtkTreePath *path,
+                                         GtkTreeIter *iter, gpointer data)
+{
+  gboolean toggled_item;
+  GSList **list = (GSList**) data;
+
+  gtk_tree_model_get(model, iter, FOLDERCHECK_CHECK, &toggled_item, -1);
+
+  if(toggled_item) {
+    FolderItem *item;
+    gtk_tree_model_get(model, iter, FOLDERCHECK_FOLDERITEM, &item, -1);
+    *list = g_slist_prepend(*list, item);
+  }
+
+  return FALSE;
+}
+
+/* Helper function to be used with a foreach statement of the model. Checks
+ * if a node is checked, and adds it to a list if it is. data us a (GSList**)
+ * where the result it to be stored */
+static gboolean foldercheck_foreach_update_to_list(GtkTreeModel *model,
+                                                  GtkTreePath *path,
+                                                  GtkTreeIter *iter,
+                                                  gpointer data)
+{
+  gchar *ident_tree, *ident_list;
+  FolderItem *item;
+  GSList *walk;
+  gboolean toggle_item = FALSE;
+  SpecificFolderArrayEntry *entry = (SpecificFolderArrayEntry*) data;
+
+  gtk_tree_model_get(model, iter, FOLDERCHECK_FOLDERITEM, &item, -1);
+
+  if(item->path != NULL)
+    ident_tree = folder_item_get_identifier(item);
+  else
+    return FALSE;
+
+  for(walk = entry->list; walk != NULL; walk = g_slist_next(walk)) {
+    FolderItem *list_item = (FolderItem*) walk->data;
+    ident_list = folder_item_get_identifier(list_item);
+    if(!strcmp2(ident_list,ident_tree)) {
+      toggle_item = TRUE;
+      g_free(ident_list);
+      break;
+    }
+    g_free(ident_list);
+  }
+  g_free(ident_tree);
+
+  gtk_tree_store_set(entry->tree_store, iter, FOLDERCHECK_CHECK,
+                    toggle_item, -1);
+
+  return FALSE;
+}
+
+
+/* Callback for the folder selection dialog. Basically a wrapper around 
+ * folder_checked that first resolves the name to an ID first. */
+void notification_foldercheck_sel_folders_cb(GtkButton *button, gpointer data)
+{
+  guint id;
+  gchar *name = (gchar*) data;
+
+  id = notification_register_folder_specific_list(name);
+
+  folder_checked(id);
+}
+
+static gboolean my_folder_update_hook(gpointer source, gpointer data)
+{
+  FolderUpdateData *hookdata = (FolderUpdateData*) source;
+
+  if(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM) {
+    gint ii;
+    SpecificFolderArrayEntry *entry;
+    FolderItem *item = hookdata->item;
+
+    /* If that folder is in anywhere in the array, cut it out. */
+    for(ii = 0; ii < specific_folder_array_size; ii++) {
+      entry = foldercheck_get_entry_from_id(ii);
+      entry->list = g_slist_remove(entry->list, item);
+    } /* for all entries in the array */
+  } /* A FolderItem was deleted */
+
+  return FALSE;
+}
+
+static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+  if(event && (event->keyval == GDK_Escape)) {
+    foldercheck_cancel(NULL, data);
+    return TRUE;
+  }
+  return FALSE;
+}
diff --git a/src/plugins/notification/notification_foldercheck.h b/src/plugins/notification/notification_foldercheck.h
new file mode 100644 (file)
index 0000000..0f6f206
--- /dev/null
@@ -0,0 +1,32 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NOTIFICATION_FOLDERCHECK_H
+#define NOTIFICATION_FOLDERCHECK_H NOTIFICATION_FOLDERCHECK_H
+
+#include <gtk/gtk.h>
+
+#include "pluginconfig.h"
+
+void     notification_foldercheck_sel_folders_cb(GtkButton*, gpointer);
+guint    notification_register_folder_specific_list(gchar*);
+GSList*  notification_foldercheck_get_list(guint);
+void     notification_free_folder_specific_array(void);
+void     notification_foldercheck_write_array(void);
+gboolean notification_foldercheck_read_array(void);
+
+#endif /* NOTIFICATION_FOLDERCHECK_H */
diff --git a/src/plugins/notification/notification_hotkeys.c b/src/plugins/notification/notification_hotkeys.c
new file mode 100644 (file)
index 0000000..663c2f8
--- /dev/null
@@ -0,0 +1,116 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2009 Holger Berndt and the Claws Mail Team.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_HOTKEYS
+
+#include "notification_hotkeys.h"
+#include "notification_prefs.h"
+#include "notification_core.h"
+
+#include "gtkhotkey.h"
+
+#define HOTKEYS_APP_ID "claws-mail"
+#define HOTKEY_KEY_ID_TOGGLED "toggle-mainwindow"
+
+static GtkHotkeyInfo *hotkey_toggle_mainwindow = NULL;
+
+static void hotkey_toggle_mainwindow_activated(GtkHotkeyInfo *hotkey, guint event_time, gpointer data)
+{
+  g_return_if_fail(GTK_HOTKEY_IS_INFO(hotkey));
+  debug_print("Notification plugin: Toggled hide/show window due to hotkey %s activation\n", gtk_hotkey_info_get_signature(hotkey));
+  notification_toggle_hide_show_window();
+}
+
+static void unbind_toggle_mainwindow()
+{
+  GError *error;
+  GtkHotkeyRegistry *registry;
+
+  /* clean up old hotkey */
+  if(hotkey_toggle_mainwindow) {
+    if(gtk_hotkey_info_is_bound(hotkey_toggle_mainwindow)) {
+      error = NULL;
+      gtk_hotkey_info_unbind(hotkey_toggle_mainwindow, &error);
+      if(error) {
+        debug_print("Notification plugin: Failed to unbind toggle hotkey\n");
+        g_error_free(error);
+        return;
+      }
+    }
+    g_object_unref(hotkey_toggle_mainwindow);
+    hotkey_toggle_mainwindow = NULL;
+  }
+  registry = gtk_hotkey_registry_get_default();
+  if(gtk_hotkey_registry_has_hotkey(registry, HOTKEYS_APP_ID, HOTKEY_KEY_ID_TOGGLED)) {
+    error = NULL;
+    gtk_hotkey_registry_delete_hotkey(registry, HOTKEYS_APP_ID, HOTKEY_KEY_ID_TOGGLED, &error);
+    if(error) {
+      debug_print("Notification plugin: Failed to unregister toggle hotkey: %s\n", error->message);
+      g_error_free(error);
+      return;
+    }
+  }
+}
+
+static void update_hotkey_binding_toggle_mainwindow()
+{
+  GError *error;
+
+  /* don't do anything if no signature is given */
+  if(!notify_config.hotkeys_toggle_mainwindow || !strcmp(notify_config.hotkeys_toggle_mainwindow, ""))
+    return;
+
+  unbind_toggle_mainwindow();
+
+  /* (re)create hotkey info */
+  hotkey_toggle_mainwindow = gtk_hotkey_info_new(HOTKEYS_APP_ID, HOTKEY_KEY_ID_TOGGLED, notify_config.hotkeys_toggle_mainwindow, NULL);
+  if(!hotkey_toggle_mainwindow) {
+    debug_print("Notification plugin: Failed to create toggle hotkey for '%s'\n", notify_config.hotkeys_toggle_mainwindow);
+    return;
+  }
+
+  /* try to register hotkey */
+  error = NULL;
+  gtk_hotkey_info_bind(hotkey_toggle_mainwindow, &error);
+  if(error) {
+    debug_print("Notification plugin: Failed to bind toggle hotkey to '%s': %s\n", notify_config.hotkeys_toggle_mainwindow, error->message);
+    g_error_free(error);
+    return;
+  }
+
+  g_signal_connect(hotkey_toggle_mainwindow, "activated", G_CALLBACK(hotkey_toggle_mainwindow_activated), NULL);
+}
+
+void notification_hotkeys_update_bindings()
+{
+  debug_print("Notification plugin: Updating keybindings..\n");
+  if(notify_config.hotkeys_enabled) {
+    update_hotkey_binding_toggle_mainwindow();
+  }
+  else
+    notification_hotkeys_unbind_all();
+}
+
+void notification_hotkeys_unbind_all()
+{
+  debug_print("Notification plugin: Unbinding all keybindings..\n");
+  unbind_toggle_mainwindow();
+}
+
+#endif /* NOTIFICATION_HOTKEYS */
diff --git a/src/plugins/notification/notification_hotkeys.h b/src/plugins/notification/notification_hotkeys.h
new file mode 100644 (file)
index 0000000..c42eab9
--- /dev/null
@@ -0,0 +1,31 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2009 Holger Berndt and the Claws Mail Team.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NOTIFICATION_HOTKEYS_H
+#define NOTIFICATION_HOTKEYS_H NOTIFICATION_HOTKEYS_H
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_HOTKEYS
+
+#include <glib.h>
+
+void notification_hotkeys_update_bindings();
+void notification_hotkeys_unbind_all();
+
+#endif /* NOTIFICATION_HOTKEYS */
+#endif /* NOTIFICATION_HOTKEYS_H */
diff --git a/src/plugins/notification/notification_indicator.c b/src/plugins/notification/notification_indicator.c
new file mode 100644 (file)
index 0000000..c89e0bb
--- /dev/null
@@ -0,0 +1,174 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2009 Holger Berndt and the Claws Mail Team.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_INDICATOR
+
+#include "notification_indicator.h"
+#include "notification_prefs.h"
+#include "notification_core.h"
+
+#include "folder.h"
+#include "common/utils.h"
+
+#include <libindicate/server.h>
+#include <libindicate/indicator.h>
+#include <libindicate/indicator-messages.h>
+
+static IndicateServer *server = NULL;
+static GHashTable *indicators = NULL;
+static gulong mainwin_state_changed_signal_id = 0;
+
+void notification_indicator_destroy(void)
+{
+  if(indicators) {
+    g_hash_table_destroy(indicators);
+    indicators = NULL;
+  }
+  if(server) {
+    indicate_server_hide(server);
+    g_object_unref(server);
+    server = NULL;
+  }
+  if(mainwin_state_changed_signal_id != 0) {
+    MainWindow *mainwin;
+    if((mainwin = mainwindow_get_mainwindow()) != NULL)
+      g_signal_handler_disconnect(mainwin->window, mainwin_state_changed_signal_id);
+    mainwin_state_changed_signal_id = 0;
+  }
+
+}
+
+static void show_claws_mail(IndicateIndicator *indicator, guint dummy, gpointer data)
+{
+  MainWindow *mainwin;
+
+  if((mainwin = mainwindow_get_mainwindow()) == NULL)
+    return;
+
+  notification_show_mainwindow(mainwin);
+  if(data) {
+    Folder *folder = data;
+    FolderItem *item = folder->inbox;
+
+    gchar *path = folder_item_get_identifier(item);
+    mainwindow_jump_to(path, FALSE);
+    g_free(path);
+  }
+}
+
+static void set_indicator_unread_count(IndicateIndicator *indicator, gint new, gint unread)
+{
+  gchar *count_str;
+
+  count_str = g_strdup_printf("%d / %d", new, unread);
+  indicate_indicator_set_property(indicator, INDICATE_INDICATOR_MESSAGES_PROP_COUNT, count_str);
+  g_free(count_str);
+}
+
+static void create_indicators(void)
+{
+  IndicateIndicator *indicator;
+  GList *cur_mb;
+
+  indicators = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
+
+  for(cur_mb = folder_get_list(); cur_mb; cur_mb = cur_mb->next) {
+    gchar *name;
+    Folder *folder = cur_mb->data;
+
+    if(!folder->name) {
+      debug_print("Notification plugin: Warning: Ignoring unnamed mailbox in indicator applet\n");
+      continue;
+    }
+    name = g_strdup(folder->name);
+
+    indicator = indicate_indicator_new();
+    indicate_indicator_set_property(indicator, INDICATE_INDICATOR_MESSAGES_PROP_NAME, name);
+    set_indicator_unread_count(indicator, 0, 0);
+    g_object_set_data(G_OBJECT(indicator), "new_msgs", GINT_TO_POINTER(0));
+    g_object_set_data(G_OBJECT(indicator), "unread_msgs", GINT_TO_POINTER(0));
+    g_signal_connect(indicator, "user-display", G_CALLBACK (show_claws_mail), folder);
+    indicate_indicator_show(indicator);
+    g_hash_table_insert(indicators, name, indicator);
+  }
+}
+
+static gboolean mainwin_state_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data)
+{
+  if(notify_config.indicator_hide_minimized) {
+    MainWindow *mainwin;
+
+    if((mainwin = mainwindow_get_mainwindow()) == NULL)
+      return FALSE;
+
+    if((event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) && (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED)) {
+      gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin->window), TRUE);
+    }
+    else if((event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) && !(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED)) {
+      gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin->window), FALSE);
+    }
+  }
+  return FALSE;
+}
+
+void notification_update_indicator(void)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  if(!mainwin_state_changed_signal_id) {
+    MainWindow *mainwin;
+
+    if((mainwin = mainwindow_get_mainwindow()) != NULL)
+      mainwin_state_changed_signal_id = g_signal_connect(G_OBJECT(mainwin->window), "window-state-event", G_CALLBACK(mainwin_state_event), NULL);
+  }
+
+
+  if(!notify_config.indicator_enabled)
+    return;
+
+  if(!server) {
+    server = indicate_server_ref_default();
+    indicate_server_set_type (server, "message.mail");
+    indicate_server_set_desktop_file(server, get_desktop_file());
+    g_signal_connect(server, "server-display", G_CALLBACK(show_claws_mail), NULL);
+    indicate_server_show(server);
+  }
+
+  if(!indicators)
+    create_indicators();
+
+  /* check accounts for new/unread counts */
+  g_hash_table_iter_init(&iter, indicators);
+  while(g_hash_table_iter_next(&iter, &key, &value)) {
+    NotificationMsgCount count;
+    gchar *foldername = key;
+    IndicateIndicator *indicator = value;
+
+    notification_core_get_msg_count_of_foldername(foldername, &count);
+
+    set_indicator_unread_count(indicator, count.new_msgs, count.unread_msgs);
+    indicate_indicator_set_property(indicator, INDICATE_INDICATOR_MESSAGES_PROP_ATTENTION,
+                                    (count.new_msgs > 0) ? "true" : "false");
+    g_object_set_data(G_OBJECT(indicator), "new_msgs", GINT_TO_POINTER(count.new_msgs));
+    g_object_set_data(G_OBJECT(indicator), "unread_msgs", GINT_TO_POINTER(count.unread_msgs));
+  }
+}
+
+#endif /* NOTIFICATION_INDICATOR */
diff --git a/src/plugins/notification/notification_indicator.h b/src/plugins/notification/notification_indicator.h
new file mode 100644 (file)
index 0000000..0460ca5
--- /dev/null
@@ -0,0 +1,31 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2009 Holger Berndt and the Claws Mail Team.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NOTIFICATION_INDICATOR_H
+#define NOTIFICATION_INDICATOR_H NOTIFICATION_INDICATOR_H
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_INDICATOR
+
+#include <glib.h>
+
+void notification_update_indicator(void);
+void notification_indicator_destroy(void);
+
+#endif /* NOTIFICATION_INDICATOR */
+#endif /* NOTIFICATION_INDICATOR_H */
diff --git a/src/plugins/notification/notification_lcdproc.c b/src/plugins/notification/notification_lcdproc.c
new file mode 100644 (file)
index 0000000..116db06
--- /dev/null
@@ -0,0 +1,172 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_LCDPROC
+
+#include "notification_lcdproc.h"
+#include "notification_prefs.h"
+#include "notification_core.h"
+
+#include "common/socket.h"
+
+#include <string.h>
+#include <sys/socket.h>
+
+#define NOTIFICATION_LCDPROC_BUFFER_SIZE 8192
+
+void notification_sock_puts(SockInfo*, gchar*);
+void notification_lcdproc_send(gchar*);
+
+static SockInfo *sock = NULL;
+
+void notification_lcdproc_connect(void)
+{
+  gint len, count;
+  gchar buf[NOTIFICATION_LCDPROC_BUFFER_SIZE];
+
+  if(!notify_config.lcdproc_enabled)
+    return;
+
+  if(sock)
+    notification_lcdproc_disconnect();
+
+  sock = sock_connect(notify_config.lcdproc_hostname,
+                     notify_config.lcdproc_port);
+  /*
+   * Quietly return when a connection fails; next attempt
+   * will be made when some folder info has been changed.
+   */
+  if(sock == NULL || sock->state == CONN_FAILED) {
+    debug_print("Could not connect to LCDd\n");
+    if(sock && sock->state == CONN_FAILED) {
+      sock_close(sock);
+      sock = NULL;
+    }
+    return;
+  }
+  else
+    debug_print("Connected to LCDd\n");
+  
+  sock_set_nonblocking_mode(sock, TRUE);
+
+  /* Friendly people say "hello" first */
+  notification_sock_puts(sock, "hello");
+
+  /* FIXME: Ouch. Is this really the way to go? */
+  count = 50;
+  len = 0;
+  while((len <= 0) && (count-- >= 0)) {
+    g_usleep(125000);
+    len = sock_read(sock, buf, NOTIFICATION_LCDPROC_BUFFER_SIZE);
+  }
+  
+/*
+ * This might not be a LCDProc server.
+ * FIXME: check LCD size, LCDd version etc
+ */
+  
+  if (len <= 0) {
+    debug_print("Notification plugin: Can't communicate with "
+               "LCDd server! Are you sure that "
+               "there is a LCDd server running on %s:%d?\n",
+               notify_config.lcdproc_hostname, notify_config.lcdproc_port);
+    notification_lcdproc_disconnect();
+    return;
+  }
+  
+  notification_lcdproc_send("client_set -name \"{Claws-Mail}\"");
+
+  notification_lcdproc_send("screen_add msg_counts");
+  notification_lcdproc_send("screen_set msg_counts -name {Claws-Mail Message Count}");
+  
+  notification_lcdproc_send("widget_add msg_counts title title");
+  notification_lcdproc_send("widget_set msg_counts title {Claws-Mail}");
+  notification_lcdproc_send("widget_add msg_counts line1 string");
+  notification_lcdproc_send("widget_add msg_counts line2 string");
+  notification_lcdproc_send("widget_add msg_counts line3 string");
+
+  notification_update_msg_counts(NULL);
+}
+
+void notification_lcdproc_disconnect(void)
+{
+  if(sock) {
+#ifndef G_OS_WIN32
+    shutdown(sock->sock, SHUT_RDWR);
+#endif
+    sock_close(sock);
+    sock = NULL;
+  }
+}
+
+void notification_update_lcdproc(void)
+{
+  NotificationMsgCount count;
+  gchar *buf;
+
+  if(!notify_config.lcdproc_enabled || !sock)
+    return;
+
+  if(!sock || sock->state == CONN_FAILED) {
+    notification_lcdproc_connect();
+    return;
+  }
+  
+  notification_core_get_msg_count(NULL, &count);
+
+  if((count.new_msgs + count.unread_msgs) > 0) {
+    buf =
+      g_strdup_printf("widget_set msg_counts line1 1 2 {%s: %d}",_("New"),
+                     count.new_msgs);
+    notification_lcdproc_send(buf);
+    buf =
+      g_strdup_printf("widget_set msg_counts line2 1 3 {%s: %d}",_("Unread"),
+                     count.unread_msgs);
+    notification_lcdproc_send(buf);
+    buf =
+      g_strdup_printf("widget_set msg_counts line3 1 4 {%s: %d}",_("Total"),
+                     count.total_msgs);
+    notification_lcdproc_send(buf);
+  }
+  else {
+    buf = g_strdup_printf("widget_set msg_counts line1 1 2 {%s}",
+                         _("No new messages"));
+    notification_lcdproc_send(buf);
+    buf = g_strdup_printf("widget_set msg_counts line2 1 3 {}");
+    notification_lcdproc_send(buf);
+    buf = g_strdup_printf("widget_set msg_counts line3 1 4 {}");
+    notification_lcdproc_send(buf);
+  }
+
+  g_free(buf);
+}
+
+void notification_sock_puts(SockInfo *socket, gchar *string)
+{
+  sock_write(socket, string, strlen(string));
+  sock_write(socket, "\n", 1);
+}
+
+void notification_lcdproc_send(gchar *string)
+{
+  notification_sock_puts(sock, string);
+  /* TODO: Check return message from LCDd */
+}
+
+#endif /* NOTIFICATION_LCDPROC */
diff --git a/src/plugins/notification/notification_lcdproc.h b/src/plugins/notification/notification_lcdproc.h
new file mode 100644 (file)
index 0000000..b543baf
--- /dev/null
@@ -0,0 +1,34 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NOTIFICATION_LCDPROC_H
+#define NOTIFICATION_LCDPROC_H NOTIFICATION_LCDPROC_H
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_LCDPROC
+
+#include <glib.h>
+
+void notification_lcdproc_connect(void);
+void notification_lcdproc_disconnect(void);
+
+void notification_update_lcdproc(void);
+
+#endif /* NOTIFICATION_LCDPROC */
+
+#endif /* include guard */
diff --git a/src/plugins/notification/notification_pixbuf.c b/src/plugins/notification/notification_pixbuf.c
new file mode 100644 (file)
index 0000000..c14ce24
--- /dev/null
@@ -0,0 +1,97 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "notification_pixbuf.h"
+
+/* The following files were created from the respective .png or
+ * xpm files with the command 
+ * gdk-pixbuf-csource --raw --name=<name> file.png > <name>.h
+ */
+/* From the Claws-Mail distribution */
+#include "raw_claws_mail_logo_64x64.h"
+
+#include "stock_pixmap.h"
+
+static GdkPixbuf* notification_pixbuf[NOTIFICATION_PIXBUF_LAST];
+
+GdkPixbuf* notification_pixbuf_get(NotificationPixbuf wanted)
+{
+  if(!notification_pixbuf[wanted]) {
+    switch(wanted) {
+    case NOTIFICATION_CM_LOGO_64x64:
+      notification_pixbuf[wanted] =
+                               gdk_pixbuf_new_from_inline(-1, raw_claws_mail_logo_64x64,
+                                                                                                                                        FALSE, NULL);
+      break;
+    case NOTIFICATION_TRAYICON_NEWMAIL:
+                       stock_pixbuf_gdk(NULL, STOCK_PIXMAP_TRAY_NEWMAIL, &(notification_pixbuf[wanted]));
+      g_object_ref(notification_pixbuf[wanted]);
+      break;
+    case NOTIFICATION_TRAYICON_NEWMAIL_OFFLINE:
+                       stock_pixbuf_gdk(NULL, STOCK_PIXMAP_TRAY_NEWMAIL_OFFLINE, &(notification_pixbuf[wanted]));
+      g_object_ref(notification_pixbuf[wanted]);
+      break;
+    case NOTIFICATION_TRAYICON_NEWMARKEDMAIL:
+                       stock_pixbuf_gdk(NULL, STOCK_PIXMAP_TRAY_NEWMARKEDMAIL, &(notification_pixbuf[wanted]));
+      g_object_ref(notification_pixbuf[wanted]);
+      break;
+    case NOTIFICATION_TRAYICON_NEWMARKEDMAIL_OFFLINE:
+                       stock_pixbuf_gdk(NULL, STOCK_PIXMAP_TRAY_NEWMARKEDMAIL_OFFLINE, &(notification_pixbuf[wanted]));
+      g_object_ref(notification_pixbuf[wanted]);
+      break;
+    case NOTIFICATION_TRAYICON_NOMAIL:
+                       stock_pixbuf_gdk(NULL, STOCK_PIXMAP_TRAY_NOMAIL, &(notification_pixbuf[wanted]));
+      g_object_ref(notification_pixbuf[wanted]);
+      break;
+    case NOTIFICATION_TRAYICON_NOMAIL_OFFLINE:
+                       stock_pixbuf_gdk(NULL, STOCK_PIXMAP_TRAY_NOMAIL_OFFLINE, &(notification_pixbuf[wanted]));
+      g_object_ref(notification_pixbuf[wanted]);
+      break;
+    case NOTIFICATION_TRAYICON_UNREADMAIL:
+                       stock_pixbuf_gdk(NULL, STOCK_PIXMAP_TRAY_UNREADMAIL, &(notification_pixbuf[wanted]));
+      g_object_ref(notification_pixbuf[wanted]);
+      break;
+    case NOTIFICATION_TRAYICON_UNREADMAIL_OFFLINE:
+                       stock_pixbuf_gdk(NULL, STOCK_PIXMAP_TRAY_UNREADMAIL_OFFLINE, &(notification_pixbuf[wanted]));
+      g_object_ref(notification_pixbuf[wanted]);
+      break;
+    case NOTIFICATION_TRAYICON_UNREADMARKEDMAIL:
+                       stock_pixbuf_gdk(NULL, STOCK_PIXMAP_TRAY_UNREADMARKEDMAIL, &(notification_pixbuf[wanted]));
+      g_object_ref(notification_pixbuf[wanted]);
+      break;
+    case NOTIFICATION_TRAYICON_UNREADMARKEDMAIL_OFFLINE:
+                       stock_pixbuf_gdk(NULL, STOCK_PIXMAP_TRAY_UNREADMARKEDMAIL_OFFLINE, &(notification_pixbuf[wanted]));
+      g_object_ref(notification_pixbuf[wanted]);
+      break;
+    case NOTIFICATION_PIXBUF_LAST:
+      break;
+    }
+  }
+  return notification_pixbuf[wanted];
+}
+
+void notification_pixbuf_free_all(void)
+{
+  gint ii;
+
+  for(ii = NOTIFICATION_CM_LOGO_64x64; ii < NOTIFICATION_PIXBUF_LAST; ii++) {
+    if(notification_pixbuf[ii]) {
+      g_object_unref(notification_pixbuf[ii]);
+      notification_pixbuf[ii] = NULL;
+    }
+  }
+}
diff --git a/src/plugins/notification/notification_pixbuf.h b/src/plugins/notification/notification_pixbuf.h
new file mode 100644 (file)
index 0000000..25392b3
--- /dev/null
@@ -0,0 +1,47 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NOTIFICATION_PIXBUF_H
+#define NOTIFICATION_PIXBUF_H NOTIFICATION_PIXBUF_H
+
+#include <gdk/gdk.h>
+
+/* By convention, the online icons for the trayicon are 
+ * immediately followed by the offline icons, so one can do
+ * offline = array[online+1] */
+typedef enum {
+  NOTIFICATION_CM_LOGO_64x64 = 0,
+  NOTIFICATION_TRAYICON_NEWMAIL,
+  NOTIFICATION_TRAYICON_NEWMAIL_OFFLINE,
+  NOTIFICATION_TRAYICON_NEWMARKEDMAIL,
+  NOTIFICATION_TRAYICON_NEWMARKEDMAIL_OFFLINE,
+  NOTIFICATION_TRAYICON_NOMAIL,
+  NOTIFICATION_TRAYICON_NOMAIL_OFFLINE,
+  NOTIFICATION_TRAYICON_UNREADMAIL,
+  NOTIFICATION_TRAYICON_UNREADMAIL_OFFLINE,
+  NOTIFICATION_TRAYICON_UNREADMARKEDMAIL,
+  NOTIFICATION_TRAYICON_UNREADMARKEDMAIL_OFFLINE,
+  NOTIFICATION_PIXBUF_LAST
+} NotificationPixbuf;
+
+/* The reference to the returned GdkPixbuf's belongs to notification_pixbuf,
+ * and shall not be removed outside. */
+GdkPixbuf* notification_pixbuf_get(NotificationPixbuf);
+
+void notification_pixbuf_free_all(void);
+
+#endif /* NOTIFICATION_PIXBUF_H */
diff --git a/src/plugins/notification/notification_plugin.c b/src/plugins/notification/notification_plugin.c
new file mode 100644 (file)
index 0000000..ef46493
--- /dev/null
@@ -0,0 +1,479 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pluginconfig.h"
+
+#include "common/claws.h"
+#include "common/version.h"
+#include "common/utils.h"
+#include "common/defs.h"
+#include "folder.h"
+#include "common/hooks.h"
+#include "procmsg.h"
+#include "mainwindow.h"
+#include "main.h"
+#include "gtk/gtkutils.h"
+
+#include "notification_plugin.h"
+#include "notification_core.h"
+#include "notification_prefs.h"
+#include "notification_banner.h"
+#include "notification_lcdproc.h"
+#include "notification_trayicon.h"
+#include "notification_indicator.h"
+#include "notification_hotkeys.h"
+#include "notification_foldercheck.h"
+#include "notification_pixbuf.h"
+#include "plugin.h"
+
+#if HAVE_LIBNOTIFY
+#  include <libnotify/notify.h>
+#endif
+
+
+static gboolean my_folder_item_update_hook(gpointer, gpointer);
+static gboolean my_folder_update_hook(gpointer, gpointer);
+static gboolean my_msginfo_update_hook(gpointer, gpointer);
+static gboolean my_offline_switch_hook(gpointer, gpointer);
+static gboolean my_main_window_close_hook(gpointer, gpointer);
+static gboolean my_main_window_got_iconified_hook(gpointer, gpointer);
+static gboolean my_account_list_changed_hook(gpointer, gpointer);
+static gboolean my_update_theme_hook(gpointer, gpointer);
+
+#ifdef NOTIFICATION_TRAYICON
+static gboolean trayicon_startup_idle(gpointer);
+#endif
+
+static guint hook_f_item;
+static guint hook_f;
+static guint hook_m_info;
+static guint hook_offline;
+static guint hook_mw_close;
+static guint hook_got_iconified;
+static guint hook_account;
+static guint hook_theme_changed;
+
+#ifdef NOTIFICATION_BANNER
+static GSList* banner_collected_msgs;
+#endif
+
+void notification_update_urgency_hint(void)
+{
+  MainWindow *mainwin;
+  mainwin = mainwindow_get_mainwindow();
+  if(mainwin) {
+    NotificationMsgCount count;
+    gboolean active;
+    active = FALSE;
+    if(notify_config.urgency_hint_new || notify_config.urgency_hint_unread) {
+      notification_core_get_msg_count(NULL, &count);
+      if(notify_config.urgency_hint_new)
+        active = (active || (count.new_msgs > 0));
+      if(notify_config.urgency_hint_unread)
+        active = (active || (count.unread_msgs > 0));
+    }
+    gtk_window_set_urgency_hint(GTK_WINDOW(mainwin->window), active);
+  }
+}
+
+static gboolean my_account_list_changed_hook(gpointer source,
+                                            gpointer data)
+{
+  gboolean retVal = FALSE;
+
+#ifdef NOTIFICATION_TRAYICON
+  notification_update_msg_counts(NULL);
+  retVal = notification_trayicon_account_list_changed(source, data);
+
+#endif
+  return retVal;
+}
+
+static gboolean my_update_theme_hook(gpointer source, gpointer data)
+{
+  notification_pixbuf_free_all();
+#ifdef NOTIFICATION_TRAYICON
+  notification_update_trayicon();
+#endif
+  return FALSE;
+}
+
+static gboolean my_main_window_got_iconified_hook(gpointer source,
+                                                 gpointer data)
+{
+  gboolean retVal = FALSE;
+#ifdef NOTIFICATION_TRAYICON
+  notification_update_msg_counts(NULL);
+  retVal = notification_trayicon_main_window_got_iconified(source, data);
+#endif
+  return retVal;
+}
+
+static gboolean my_main_window_close_hook(gpointer source, gpointer data)
+{
+  gboolean retVal = FALSE;
+#ifdef NOTIFICATION_TRAYICON
+  notification_update_msg_counts(NULL);
+  retVal = notification_trayicon_main_window_close(source, data);
+#endif
+  return retVal;
+}
+
+static gboolean my_offline_switch_hook(gpointer source, gpointer data)
+{
+#ifdef NOTIFICATION_TRAYICON
+  notification_update_msg_counts(NULL);
+#endif
+  return FALSE;
+}
+
+static gboolean my_folder_item_update_hook(gpointer source, gpointer data)
+{
+  FolderItemUpdateData *update_data = source;
+  FolderType ftype;
+  gchar *uistr;
+
+  g_return_val_if_fail(source != NULL, FALSE);
+
+#if defined(NOTIFICATION_LCDPROC) || defined(NOTIFICATION_TRAYICON) || defined(NOTIFICATION_INDICATOR)
+    notification_update_msg_counts(NULL);
+#else
+    if(notify_config.urgency_hint)
+       notification_update_msg_counts(NULL);
+#endif
+
+  /* Check if the folder types is to be notified about */
+  ftype = update_data->item->folder->klass->type;
+  uistr = update_data->item->folder->klass->uistr;
+  if(!notify_include_folder_type(ftype, uistr))
+    return FALSE;
+
+  if(update_data->update_flags & F_ITEM_UPDATE_MSGCNT) {
+#ifdef NOTIFICATION_BANNER
+    notification_update_banner();
+#endif
+#if defined(NOTIFICATION_POPUP) || defined(NOTIFICATION_COMMAND)
+    notification_new_unnotified_msgs(update_data);
+#endif
+  }
+  return FALSE;
+}
+
+static gboolean my_folder_update_hook(gpointer source, gpointer data)
+{
+  FolderUpdateData *hookdata;
+
+  g_return_val_if_fail(source != NULL, FALSE);
+  hookdata = source;
+
+#if defined(NOTIFICATION_LCDPROC) || defined(NOTIFICATION_TRAYICON)
+  if(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
+    notification_update_msg_counts(hookdata->item);
+  else
+    notification_update_msg_counts(NULL);
+#endif
+
+  return FALSE;
+
+}
+
+
+static gboolean my_msginfo_update_hook(gpointer source, gpointer data)
+{
+  return notification_notified_hash_msginfo_update((MsgInfoUpdate*)source);
+}
+
+gint plugin_init(gchar **error)
+{
+  gchar *rcpath;
+
+  /* Version check */
+  /* No be able to test against new-contacts */
+  if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,8,1,46),
+                          VERSION_NUMERIC, _("Notification"), error))
+    return -1;
+
+  /* Check if threading is enabled */
+  if(!g_thread_supported()) {
+    *error = g_strdup(_("The Notification plugin needs threading support."));
+    return -1;
+  }
+
+  hook_f_item = hooks_register_hook(FOLDER_ITEM_UPDATE_HOOKLIST,
+                                   my_folder_item_update_hook, NULL);
+  if(hook_f_item == (guint) -1) {
+    *error = g_strdup(_("Failed to register folder item update hook in the "
+                       "Notification plugin"));
+    return -1;
+  }
+
+  hook_f = hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
+                              my_folder_update_hook, NULL);
+  if(hook_f == (guint) -1) {
+    *error = g_strdup(_("Failed to register folder update hook in the "
+                       "Notification plugin"));
+    hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
+    return -1;
+  }
+
+
+  hook_m_info = hooks_register_hook(MSGINFO_UPDATE_HOOKLIST,
+                                   my_msginfo_update_hook, NULL);
+  if(hook_m_info == (guint) -1) {
+    *error = g_strdup(_("Failed to register msginfo update hook in the "
+                       "Notification plugin"));
+    hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
+    hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
+    return -1;
+  }
+
+  hook_offline = hooks_register_hook(OFFLINE_SWITCH_HOOKLIST,
+                                    my_offline_switch_hook, NULL);
+  if(hook_offline == (guint) -1) {
+    *error = g_strdup(_("Failed to register offline switch hook in the "
+                       "Notification plugin"));
+    hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
+    hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
+    hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, hook_m_info);
+    return -1;
+  }
+
+  hook_mw_close = hooks_register_hook(MAIN_WINDOW_CLOSE,
+                                     my_main_window_close_hook, NULL);
+  if(hook_mw_close == (guint) -1) {
+    *error = g_strdup(_("Failed to register main window close hook in the "
+                       "Notification plugin"));
+    hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
+    hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
+    hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, hook_m_info);
+    hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, hook_offline);
+    return -1;
+  }
+
+  hook_got_iconified = hooks_register_hook(MAIN_WINDOW_GOT_ICONIFIED,
+                                          my_main_window_got_iconified_hook,
+                                          NULL);
+  if(hook_got_iconified == (guint) -1) {
+    *error = g_strdup(_("Failed to register got iconified hook in the "
+                       "Notification plugin"));
+    hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
+    hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
+    hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, hook_m_info);
+    hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, hook_offline);
+    hooks_unregister_hook(MAIN_WINDOW_CLOSE, hook_mw_close);
+    return -1;
+  }
+
+  hook_account = hooks_register_hook(ACCOUNT_LIST_CHANGED_HOOKLIST,
+                                    my_account_list_changed_hook, NULL);
+  if (hook_account == (guint) -1) {
+    *error = g_strdup(_("Failed to register account list changed hook in the "
+                       "Notification plugin"));
+    hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
+    hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
+    hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, hook_m_info);
+    hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, hook_offline);
+    hooks_unregister_hook(MAIN_WINDOW_CLOSE, hook_mw_close);
+    hooks_unregister_hook(MAIN_WINDOW_GOT_ICONIFIED, hook_got_iconified);
+    return -1;
+  }
+
+  hook_theme_changed = hooks_register_hook(THEME_CHANGED_HOOKLIST, my_update_theme_hook, NULL);
+  if(hook_theme_changed == (guint)-1) {
+    *error = g_strdup(_("Failed to register theme change hook int the "
+      "Notification plugin"));
+    hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
+    hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
+    hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, hook_m_info);
+    hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, hook_offline);
+    hooks_unregister_hook(MAIN_WINDOW_CLOSE, hook_mw_close);
+    hooks_unregister_hook(MAIN_WINDOW_GOT_ICONIFIED, hook_got_iconified);
+    hooks_unregister_hook(ACCOUNT_LIST_CHANGED_HOOKLIST, hook_account);
+    return -1;
+  }
+
+  /* Configuration */
+  prefs_set_default(notify_param);
+  rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+  prefs_read_config(notify_param, "NotificationPlugin", rcpath, NULL);
+  g_free(rcpath);
+
+  /* Folder specific stuff */
+  notification_foldercheck_read_array();
+
+  notification_notified_hash_startup_init();
+
+  notify_gtk_init();
+
+#ifdef NOTIFICATION_BANNER
+  notification_update_banner();
+#endif
+#ifdef NOTIFICATION_LCDPROC
+  notification_lcdproc_connect();
+#endif
+#ifdef NOTIFICATION_TRAYICON
+  if(notify_config.trayicon_enabled &&
+                notify_config.trayicon_hide_at_startup && claws_is_starting()) {
+    MainWindow *mainwin = mainwindow_get_mainwindow();
+
+               g_idle_add(trayicon_startup_idle,NULL);
+    if(mainwin && gtk_widget_get_visible(GTK_WIDGET(mainwin->window)))
+      main_window_hide(mainwin);
+    main_set_show_at_startup(FALSE);
+  }
+#endif
+  my_account_list_changed_hook(NULL,NULL);
+
+  if(notify_config.urgency_hint_new || notify_config.urgency_hint_unread)
+       notification_update_msg_counts(NULL);
+
+#ifdef NOTIFICATION_HOTKEYS
+  notification_hotkeys_update_bindings();
+#endif
+
+  debug_print("Notification plugin loaded\n");
+
+  return 0;
+}
+
+gboolean plugin_done(void)
+{
+  hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
+  hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
+  hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, hook_m_info);
+  hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, hook_offline);
+  hooks_unregister_hook(MAIN_WINDOW_CLOSE, hook_mw_close);
+  hooks_unregister_hook(MAIN_WINDOW_GOT_ICONIFIED, hook_got_iconified);
+  hooks_unregister_hook(ACCOUNT_LIST_CHANGED_HOOKLIST, hook_account);
+  hooks_unregister_hook(THEME_CHANGED_HOOKLIST, hook_theme_changed);
+
+  notify_save_config();
+
+  notify_gtk_done();
+
+  /* foldercheck cleanup */
+  notification_foldercheck_write_array();
+  notification_free_folder_specific_array();
+
+#ifdef NOTIFICATION_BANNER
+  notification_collected_msgs_free(banner_collected_msgs);
+  banner_collected_msgs = NULL;
+  notification_banner_destroy();
+#endif
+#ifdef NOTIFICATION_LCDPROC
+  notification_lcdproc_disconnect();
+#endif
+#ifdef NOTIFICATION_TRAYICON
+  notification_trayicon_destroy();
+#endif
+#ifdef NOTIFICATION_INDICATOR
+  notification_indicator_destroy();
+#endif
+  notification_core_free();
+
+#ifdef HAVE_LIBNOTIFY
+  if(notify_is_initted())
+    notify_uninit();
+#endif
+
+#ifdef NOTIFICATION_HOTKEYS
+  notification_hotkeys_unbind_all();
+#endif
+
+  notification_pixbuf_free_all();
+
+  debug_print("Notification plugin unloaded\n");
+
+  /* Returning FALSE here means that g_module_close() will not be called on the plugin.
+   * This is necessary, as needed libraries are not designed to be unloaded. */
+  return FALSE;
+}
+
+const gchar *plugin_name(void)
+{
+  return _("Notification");
+}
+
+const gchar *plugin_desc(void)
+{
+  return _("This plugin provides various ways "
+    "to notify the user of new and unread email.\n"
+    "The plugin is extensively configurable in the "
+    "plugins section of the preferences dialog.\n\n"
+    "Feedback to <berndth@gmx.de> is welcome.");
+}
+
+const gchar *plugin_type(void)
+{
+  return "GTK2";
+}
+
+const gchar *plugin_licence(void)
+{
+  return "GPL3+";
+}
+
+const gchar *plugin_version(void)
+{
+  return VERSION;
+}
+
+struct PluginFeature *plugin_provides(void)
+{
+  static struct PluginFeature features[] =
+    { {PLUGIN_NOTIFIER, N_("Various tools")},
+      {PLUGIN_NOTHING, NULL}};
+  return features;
+}
+
+#ifdef NOTIFICATION_BANNER
+void notification_update_banner(void)
+{
+  notification_collected_msgs_free(banner_collected_msgs);
+  banner_collected_msgs = NULL;
+
+  if(notify_config.banner_show != NOTIFY_BANNER_SHOW_NEVER) {
+    guint id;
+    GSList *folder_list = NULL;
+
+    if(notify_config.banner_folder_specific) {
+      id = notification_register_folder_specific_list
+                               (BANNER_SPECIFIC_FOLDER_ID_STR);
+      folder_list = notification_foldercheck_get_list(id);
+    }
+
+    if(!(notify_config.banner_folder_specific && (folder_list == NULL)))
+      banner_collected_msgs =
+                               notification_collect_msgs(notify_config.banner_include_unread,
+                                                                                                                                       notify_config.banner_folder_specific ?
+                                                                                                                                       folder_list : NULL, notify_config.banner_max_msgs);
+  }
+
+  notification_banner_show(banner_collected_msgs);
+}
+#endif
+
+#ifdef NOTIFICATION_TRAYICON
+static gboolean trayicon_startup_idle(gpointer data)
+{
+       /* if the trayicon is not available,
+                simulate click on it to show mainwindow */
+       if(!notification_trayicon_is_available())
+               notification_trayicon_on_activate(NULL,data);
+       return FALSE;
+}
+#endif
diff --git a/src/plugins/notification/notification_plugin.h b/src/plugins/notification/notification_plugin.h
new file mode 100644 (file)
index 0000000..d2a0b63
--- /dev/null
@@ -0,0 +1,29 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NOTIFICATION_PLUGIN_H
+#define NOTIFICATION_PLUGIN_H NOTIFICATION_PLUGIN_H
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_BANNER
+void notification_update_banner(void);
+#endif
+
+void notification_update_urgency_hint(void);
+
+#endif /* NOTIFICATION_PLUGIN_H */
diff --git a/src/plugins/notification/notification_popup.c b/src/plugins/notification/notification_popup.c
new file mode 100644 (file)
index 0000000..c50730f
--- /dev/null
@@ -0,0 +1,677 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_POPUP
+
+#include <gtk/gtk.h>
+
+#include "mainwindow.h"
+#include "procmsg.h"
+#include "folder.h"
+#ifndef USE_NEW_ADDRBOOK
+    #include "addrindex.h"
+#endif
+#include "common/utils.h"
+#include "gtk/gtkutils.h"
+
+#include "notification_popup.h"
+#include "notification_prefs.h"
+#include "notification_foldercheck.h"
+#include "notification_pixbuf.h"
+#include "notification_core.h"
+
+#ifdef HAVE_LIBNOTIFY
+#  include <libnotify/notify.h>
+#endif
+
+#ifndef NOTIFY_CHECK_VERSION
+# define NOTIFY_CHECK_VERSION(a,b,c) 0
+#endif
+
+typedef struct {
+  gint count;
+  gchar *msg_path;
+#ifdef HAVE_LIBNOTIFY
+  NotifyNotification *notification;
+  GError *error;
+#else /* !HAVE_LIBNOTIFY */
+  guint timeout_id;
+  GtkWidget *window;
+  GtkWidget *frame;
+  GtkWidget *event_box;
+  GtkWidget *vbox;
+  GtkWidget *label1;
+  GtkWidget *label2;
+#endif
+} NotificationPopup;
+
+G_LOCK_DEFINE_STATIC(popup);
+
+#ifdef HAVE_LIBNOTIFY
+static NotificationPopup popup[F_TYPE_LAST];
+
+static void popup_timeout_fun(NotifyNotification*, gpointer data);
+
+static gboolean notification_libnotify_create(MsgInfo*, NotificationFolderType);
+static gboolean notification_libnotify_add_msg(MsgInfo*, NotificationFolderType);
+static void default_action_cb(NotifyNotification*, const char*,void*);
+static void notification_libnotify_free_func(gpointer);
+
+#else
+static NotificationPopup popup;
+
+static gboolean popup_timeout_fun(gpointer data);
+
+static gboolean notification_popup_add_msg(MsgInfo*);
+static gboolean notification_popup_create(MsgInfo*);
+static gboolean notification_popup_button(GtkWidget*, GdkEventButton*, gpointer);
+#endif
+
+
+void notification_popup_msg(MsgInfo *msginfo)
+{
+  gboolean retval;
+  FolderType ftype;
+  NotificationPopup *ppopup;
+#if HAVE_LIBNOTIFY
+  gchar *uistr;
+#endif
+  NotificationFolderType nftype;
+
+  nftype = F_TYPE_MAIL;
+
+  if(!msginfo || !notify_config.popup_show)
+    return;
+
+  if(notify_config.popup_folder_specific) {
+    guint id;
+    GSList *list;
+    gchar *identifier;
+    gboolean found = FALSE;
+
+    if(!(msginfo->folder))
+      return;
+
+    identifier = folder_item_get_identifier(msginfo->folder);
+
+    id =
+      notification_register_folder_specific_list(POPUP_SPECIFIC_FOLDER_ID_STR);
+    list = notification_foldercheck_get_list(id);
+    for(; (list != NULL) && !found; list = g_slist_next(list)) {
+      gchar *list_identifier;
+      FolderItem *list_item = (FolderItem*) list->data;
+
+      list_identifier = folder_item_get_identifier(list_item);
+      if(!strcmp2(list_identifier, identifier))
+       found = TRUE;
+
+      g_free(list_identifier);
+    }
+    g_free(identifier);
+
+    if(!found)
+      return;
+  }
+
+  ftype = msginfo->folder->folder->klass->type;
+
+  G_LOCK(popup);
+#ifdef HAVE_LIBNOTIFY
+  /* Check out which type to notify about */
+  switch(ftype) {
+  case F_MH:
+  case F_MBOX:
+  case F_MAILDIR:
+  case F_IMAP:
+    nftype = F_TYPE_MAIL;
+    break;
+  case F_NEWS:
+    nftype = F_TYPE_NEWS;
+    break;
+  case F_UNKNOWN:
+    if((uistr = msginfo->folder->folder->klass->uistr) == NULL) {
+      G_UNLOCK(popup);
+      return;
+    }
+    else if(!strcmp(uistr, "vCalendar"))
+      nftype = F_TYPE_CALENDAR;
+    else if(!strcmp(uistr, "RSSyl"))
+      nftype = F_TYPE_RSS;
+    else {
+      debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
+      G_UNLOCK(popup);
+      return;
+    }
+    break;
+  default:
+    debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
+    G_UNLOCK(popup);
+    return;
+  }
+
+  ppopup = &(popup[nftype]);
+  retval = notification_libnotify_add_msg(msginfo, nftype);
+#else /* !HAVE_LIBNOTIFY */
+  ppopup = &popup;
+  retval = notification_popup_add_msg(msginfo);
+
+  /* Renew timeout only when the above call was successful */
+  if(retval) {
+    if(ppopup->timeout_id)
+      g_source_remove(ppopup->timeout_id);
+    ppopup->timeout_id = g_timeout_add(notify_config.popup_timeout,
+                       popup_timeout_fun,
+                       GINT_TO_POINTER(nftype));
+  }
+
+  #endif /* !HAVE_LIBNOTIFY */
+
+  G_UNLOCK(popup);
+
+#ifndef HAVE_LIBNOTIFY
+  /* GUI update */
+  while(gtk_events_pending())
+    gtk_main_iteration();
+#endif /* !HAVE_LIBNOTIFY */
+
+}
+
+#ifdef HAVE_LIBNOTIFY
+static void popup_timeout_fun(NotifyNotification *nn, gpointer data)
+{
+  NotificationPopup *ppopup;
+  NotificationFolderType nftype;
+
+  nftype = GPOINTER_TO_INT(data);
+
+  G_LOCK(popup);
+
+  ppopup = &(popup[nftype]);
+
+  g_object_unref(G_OBJECT(ppopup->notification));
+  ppopup->notification = NULL;
+  g_clear_error(&(ppopup->error));
+
+  if(ppopup->msg_path) {
+    g_free(ppopup->msg_path);
+    ppopup->msg_path = NULL;
+  }
+  ppopup->count = 0;
+  G_UNLOCK(popup);
+  debug_print("Notification Plugin: Popup closed due to timeout.\n");
+}
+
+#else
+static gboolean popup_timeout_fun(gpointer data)
+{
+  NotificationPopup *ppopup;
+  NotificationFolderType nftype;
+
+  nftype = GPOINTER_TO_INT(data);
+
+  G_LOCK(popup);
+
+  ppopup = &popup;
+  if(ppopup->window) {
+    gtk_widget_destroy(ppopup->window);
+    ppopup->window = NULL;
+  }
+  ppopup->timeout_id = 0;
+
+  if(ppopup->msg_path) {
+    g_free(ppopup->msg_path);
+    ppopup->msg_path = NULL;
+  }
+  ppopup->count = 0;
+  G_UNLOCK(popup);
+  debug_print("Notification Plugin: Popup closed due to timeout.\n");
+  return FALSE;
+}
+#endif
+
+#ifdef HAVE_LIBNOTIFY
+static void default_action_cb(NotifyNotification *notification,
+                             const char *action,
+                             void *user_data)
+{
+  if(strcmp("default", action))
+    return;
+
+  MainWindow *mainwin;
+  mainwin = mainwindow_get_mainwindow();
+  if(mainwin) {
+    NotificationFolderType nftype;
+
+    /* Let mainwindow pop up */
+    notification_show_mainwindow(mainwin);
+    /* If there is only one new mail message, jump to this message */
+    nftype = (NotificationFolderType)GPOINTER_TO_INT(user_data);
+    if(nftype == F_TYPE_MAIL) {
+      if(popup[F_TYPE_MAIL].count == 1) {
+       gchar *select_str;
+       G_LOCK(popup);
+       select_str = g_strdup(popup[F_TYPE_MAIL].msg_path);
+       G_UNLOCK(popup);
+       debug_print("Select message %s\n", select_str);
+       mainwindow_jump_to(select_str, FALSE);
+       g_free(select_str);
+      }
+    }
+  }
+}
+
+static gboolean notification_libnotify_create(MsgInfo *msginfo,
+                                             NotificationFolderType nftype)
+{
+  GdkPixbuf *pixbuf;
+  NotificationPopup *ppopup;
+  gchar *summary = NULL;
+  gchar *text = NULL;
+  gchar *utf8_str = NULL;
+  gchar *subj = NULL;
+  gchar *from = NULL;
+  gchar *foldname = NULL;
+  GList *caps = NULL;
+  gboolean support_actions = FALSE;
+
+  g_return_val_if_fail(msginfo, FALSE);
+
+  ppopup = &(popup[nftype]);
+
+  /* init libnotify if necessary */
+  if(!notify_is_initted()) {
+    if(!notify_init("claws-mail")) {
+      debug_print("Notification Plugin: Failed to initialize libnotify. "
+                 "No popup will be shown.\n");
+      return FALSE;
+    }
+  }
+
+  switch(nftype) {
+  case F_TYPE_MAIL:
+    summary = _("New Mail message");
+    from    = notification_libnotify_sanitize_str(msginfo->from ?
+                                                  msginfo->from : _("(No From)"));
+    subj    = notification_libnotify_sanitize_str(msginfo->subject ?
+                                                  msginfo->subject : _("(No Subject)"));
+       if (notify_config.popup_display_folder_name) {
+               foldname = notification_libnotify_sanitize_str(msginfo->folder->path);
+       text = g_strconcat(from,"\n\n", subj, "\n\n", foldname, NULL);
+       }
+       else
+               text = g_strconcat(from, "\n\n",subj, NULL);
+
+    /* Make sure text is valid UTF8 */
+    utf8_str = notification_validate_utf8_str(text);
+    g_free(text);
+
+    if(from) g_free(from);
+    if(subj) g_free(subj);
+    if(foldname) g_free(foldname);
+    break;
+  case F_TYPE_NEWS:
+    summary = _("New News post");
+    utf8_str    = g_strdup(_("A new message arrived"));
+    break;
+  case F_TYPE_CALENDAR:
+    summary = _("New Calendar message");
+    utf8_str    = g_strdup(_("A new calendar message arrived"));
+    break;
+  case F_TYPE_RSS:
+    summary = _("New RSS feed article");
+    utf8_str = g_strdup(_("A new article in a RSS feed arrived"));
+    break;
+  default:
+    summary = _("New unknown message");
+    utf8_str = g_strdup(_("Unknown message type arrived"));
+    break;
+  }
+
+  ppopup->notification = notify_notification_new(summary, utf8_str, NULL
+#if !NOTIFY_CHECK_VERSION(0, 7, 0)
+      , NULL
+#endif
+      );
+  g_free(utf8_str);
+  if(ppopup->notification == NULL) {
+    debug_print("Notification Plugin: Failed to create a new "
+               "notification.\n");
+    return FALSE;
+  }
+
+  caps = notify_get_server_caps();
+    if(caps != NULL) {
+      GList *c;
+      for(c = caps; c != NULL; c = c->next) {
+       if(strcmp((char*)c->data, "actions") == 0 ) {
+         support_actions = TRUE;
+         break;
+        }
+      }
+
+    g_list_foreach(caps, (GFunc)g_free, NULL);
+    g_list_free(caps);
+  }
+
+  /* Default action */
+  if (support_actions)
+    notify_notification_add_action(ppopup->notification,
+                                  "default", "Present main window",
+                                  (NotifyActionCallback)default_action_cb,
+                                  GINT_TO_POINTER(nftype),
+                                  notification_libnotify_free_func);
+
+  /* Icon */
+  pixbuf = NULL;
+#ifndef USE_NEW_ADDRBOOK
+  if(msginfo && msginfo->from) {
+    gchar *icon_path;
+    icon_path = addrindex_get_picture_file(msginfo->from);
+    if(is_file_exist(icon_path)) {
+      GError *error = NULL;
+      gint w, h;
+
+      gdk_pixbuf_get_file_info(icon_path, &w, &h);
+      if((w > 64) || (h > 64))
+       pixbuf = gdk_pixbuf_new_from_file_at_scale(icon_path,
+                                                  64, 64, TRUE, &error);
+      else
+       pixbuf = gdk_pixbuf_new_from_file(icon_path, &error);
+
+      if(!pixbuf) {
+       debug_print("Could not load picture file: %s\n",
+                   error ? error->message : "no details");
+       g_error_free(error);
+      }
+    }
+    else
+      debug_print("Picture path does not exist: %s\n",icon_path);
+    g_free(icon_path);
+  }
+#endif
+  if(!pixbuf)
+   pixbuf = g_object_ref(notification_pixbuf_get(NOTIFICATION_CM_LOGO_64x64));
+
+  if(pixbuf) {
+    notify_notification_set_icon_from_pixbuf(ppopup->notification, pixbuf);
+    g_object_unref(pixbuf);
+  }
+  else /* This is not fatal */
+    debug_print("Notification plugin: Icon could not be loaded.\n");
+
+  /* timeout */
+  notify_notification_set_timeout(ppopup->notification, notify_config.popup_timeout);
+
+  /* Category */
+  notify_notification_set_category(ppopup->notification, "email.arrived");
+
+  /* get notified on bubble close */
+  g_signal_connect(G_OBJECT(popup->notification), "closed", G_CALLBACK(popup_timeout_fun), NULL);
+
+  /* Show the popup */
+  notify_notification_set_hint_string(ppopup->notification, "desktop-entry", "claws-mail");
+  if(!notify_notification_show(ppopup->notification, &(ppopup->error))) {
+    debug_print("Notification Plugin: Failed to send notification: %s\n",
+               ppopup->error->message);
+    g_clear_error(&(ppopup->error));
+    g_object_unref(G_OBJECT(ppopup->notification));
+    ppopup->notification = NULL;
+    return FALSE;
+  }
+
+  debug_print("Notification Plugin: Popup created with libnotify.\n");
+  ppopup->count = 1;
+
+  /* Store path to message */
+  if(nftype == F_TYPE_MAIL) {
+    if(msginfo->folder && msginfo->folder) {
+      gchar *ident;
+      ident = folder_item_get_identifier(msginfo->folder);
+      ppopup->msg_path = g_strdup_printf("%s%s%u", ident,G_DIR_SEPARATOR_S,
+                                        msginfo->msgnum);
+      g_free(ident);
+    }
+    else
+      ppopup->msg_path = NULL;
+  }
+
+  return TRUE;
+}
+
+static gboolean notification_libnotify_add_msg(MsgInfo *msginfo,
+                                              NotificationFolderType nftype)
+{
+  gchar *summary;
+  gchar *text;
+  gboolean retval;
+  NotificationPopup *ppopup;
+  GdkPixbuf *pixbuf;
+
+  ppopup = &(popup[nftype]);
+
+  if(!ppopup->notification)
+    return notification_libnotify_create(msginfo,nftype);
+
+  ppopup->count++;
+
+  if(ppopup->msg_path) {
+    g_free(ppopup->msg_path);
+    ppopup->msg_path = NULL;
+  }
+
+  /* make sure we show a logo on many msg arrival */
+  pixbuf = notification_pixbuf_get(NOTIFICATION_CM_LOGO_64x64);
+  if(pixbuf)
+    notify_notification_set_icon_from_pixbuf(ppopup->notification, pixbuf);
+
+  switch(nftype) {
+  case F_TYPE_MAIL:
+    summary = _("Mail message");
+    text = g_strdup_printf(ngettext("%d new message arrived",
+                                   "%d new messages arrived",
+                                   ppopup->count), ppopup->count);
+    break;
+  case F_TYPE_NEWS:
+    summary = _("News message");
+    text = g_strdup_printf(ngettext("%d new message arrived",
+                                     "%d new messages arrived",
+                                    ppopup->count), ppopup->count);
+    break;
+  case F_TYPE_CALENDAR:
+    summary = _("Calendar message");
+    text = g_strdup_printf(ngettext("%d new calendar message arrived",
+                                     "%d new calendar messages arrived",
+                                    ppopup->count), ppopup->count);
+    break;
+  case F_TYPE_RSS:
+    summary = _("RSS news feed");
+    text = g_strdup_printf(ngettext("%d new article in a RSS feed arrived",
+                                     "%d new articles in a RSS feed arrived",
+                                    ppopup->count), ppopup->count);
+    break;
+  default:
+    /* Should not happen */
+    debug_print("Notification Plugin: Unknown folder type ignored\n");
+    return FALSE;
+  }
+
+  retval = notify_notification_update(ppopup->notification, summary,
+                                     text, NULL);
+  g_free(text);
+  if(!retval) {
+    debug_print("Notification Plugin: Failed to update notification.\n");
+    return FALSE;
+  }
+
+  /* Show the popup */
+  notify_notification_set_hint_string(ppopup->notification, "desktop-entry", "claws-mail");
+  if(!notify_notification_show(ppopup->notification, &(ppopup->error))) {
+    debug_print("Notification Plugin: Failed to send updated notification: "
+               "%s\n", ppopup->error->message);
+    g_clear_error(&(ppopup->error));
+    return FALSE;
+  }
+
+  debug_print("Notification Plugin: Popup successfully modified "
+             "with libnotify.\n");
+  return TRUE;
+}
+
+void notification_libnotify_free_func(gpointer data)
+{
+  if(popup[F_TYPE_MAIL].msg_path) {
+    g_free(popup[F_TYPE_MAIL].msg_path);
+    popup[F_TYPE_MAIL].msg_path = NULL;
+  }
+  debug_print("Freed notification data\n");
+}
+
+#else /* !HAVE_LIBNOTIFY */
+static gboolean notification_popup_add_msg(MsgInfo *msginfo)
+{
+  gchar *message;
+  NotificationPopup *ppopup;
+
+  ppopup = &popup;
+
+  if(!ppopup->window)
+    return notification_popup_create(msginfo);
+
+  ppopup->count++;
+
+  if(ppopup->msg_path) {
+    g_free(ppopup->msg_path);
+    ppopup->msg_path = NULL;
+  }
+
+  if(ppopup->label2)
+    gtk_widget_destroy(ppopup->label2);
+
+  message = g_strdup_printf(ngettext("%d new message",
+                                    "%d new messages",
+                                    ppopup->count), ppopup->count);
+  gtk_label_set_text(GTK_LABEL(ppopup->label1), message);
+  g_free(message);
+  return TRUE;
+}
+
+static gboolean notification_popup_create(MsgInfo *msginfo)
+{
+  GdkColor bg;
+  GdkColor fg;
+  NotificationPopup *ppopup;
+
+  g_return_val_if_fail(msginfo, FALSE);
+
+  ppopup = &popup;
+
+  /* Window */
+  ppopup->window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "notification_popup");
+  gtk_window_set_decorated(GTK_WINDOW(ppopup->window), FALSE);
+  gtk_window_set_keep_above(GTK_WINDOW(ppopup->window), TRUE);
+  gtk_window_set_accept_focus(GTK_WINDOW(ppopup->window), FALSE);
+  gtk_window_set_skip_taskbar_hint(GTK_WINDOW(ppopup->window), TRUE);
+  gtk_window_set_skip_pager_hint(GTK_WINDOW(ppopup->window), TRUE);
+  gtk_window_move(GTK_WINDOW(ppopup->window), notify_config.popup_root_x,
+                 notify_config.popup_root_y);
+  gtk_window_resize(GTK_WINDOW(ppopup->window), notify_config.popup_width, 1);
+  if(notify_config.popup_sticky)
+    gtk_window_stick(GTK_WINDOW(ppopup->window));
+  /* Signals */
+  gtk_widget_set_events(ppopup->window,
+                       GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+  g_signal_connect(ppopup->window, "button_press_event",
+                  G_CALLBACK(notification_popup_button), NULL);
+
+  /* Event box */
+  ppopup->event_box = gtk_event_box_new();
+  gtk_container_add(GTK_CONTAINER(ppopup->window), ppopup->event_box);
+
+  /* Frame */
+  ppopup->frame = gtk_frame_new(NULL);
+  gtk_frame_set_shadow_type(GTK_FRAME(ppopup->frame), GTK_SHADOW_ETCHED_OUT);
+  gtk_container_add(GTK_CONTAINER(ppopup->event_box), ppopup->frame);
+
+  /* Vbox with labels */
+  ppopup->vbox = gtk_vbox_new(FALSE, 2);
+  gtk_container_set_border_width(GTK_CONTAINER(ppopup->vbox), 5);
+  ppopup->label1 = gtk_label_new(msginfo->from ?
+                                 msginfo->from : _("(No From)"));
+  gtk_box_pack_start(GTK_BOX(ppopup->vbox), ppopup->label1, FALSE, FALSE, 0);
+
+  ppopup->label2 = gtk_label_new(msginfo->subject ?
+                                 msginfo->subject : _("(No Subject)"));
+  gtk_box_pack_start(GTK_BOX(ppopup->vbox), ppopup->label2, FALSE, FALSE, 0);
+
+  gtk_container_add(GTK_CONTAINER(ppopup->frame), ppopup->vbox);
+  gtk_widget_set_size_request(ppopup->vbox, notify_config.popup_width, -1);
+
+  /* Color */
+  if(notify_config.popup_enable_colors) {
+    gtkut_convert_int_to_gdk_color(notify_config.popup_color_bg,&bg);
+    gtkut_convert_int_to_gdk_color(notify_config.popup_color_fg,&fg);
+    gtk_widget_modify_bg(ppopup->event_box,GTK_STATE_NORMAL,&bg);
+    gtk_widget_modify_fg(ppopup->label1,GTK_STATE_NORMAL,&fg);
+    gtk_widget_modify_fg(ppopup->label2,GTK_STATE_NORMAL,&fg);
+  }
+
+  gtk_widget_show_all(ppopup->window);
+
+  ppopup->count = 1;
+
+  if(msginfo->folder && msginfo->folder->name) {
+      gchar *ident;
+      ident = folder_item_get_identifier(msginfo->folder);
+      ppopup->msg_path = g_strdup_printf("%s%s%u", ident,G_DIR_SEPARATOR_S,
+                                        msginfo->msgnum);
+      g_free(ident);
+  }
+
+  return TRUE;
+}
+
+static gboolean notification_popup_button(GtkWidget *widget,
+                                         GdkEventButton *event,
+                                         gpointer data)
+{
+  if(event->type == GDK_BUTTON_PRESS) {
+    if(event->button == 1) {
+      MainWindow *mainwin;
+      /* Let mainwindow pop up */
+      mainwin = mainwindow_get_mainwindow();
+      if(!mainwin)
+       return TRUE;
+      notification_show_mainwindow(mainwin);
+      /* If there is only one new mail message, jump to this message */
+      if(popup.count == 1) {
+       gchar *select_str;
+       G_LOCK(popup);
+       select_str = g_strdup(popup.msg_path);
+       G_UNLOCK(popup);
+       debug_print("Select message %s\n", select_str);
+       mainwindow_jump_to(select_str, FALSE);
+       g_free(select_str);
+      }
+    }
+  }
+  return TRUE;
+}
+#endif /* !HAVE_LIBNOTIFY */
+
+#endif /* NOTIFICATION_POPUP */
diff --git a/src/plugins/notification/notification_popup.h b/src/plugins/notification/notification_popup.h
new file mode 100644 (file)
index 0000000..a179072
--- /dev/null
@@ -0,0 +1,33 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NOTIFICATION_POPUP_H
+#define NOTIFICATION_POPUP_H NOTIFICATION_POPUP_H
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_POPUP
+
+#include "procmsg.h"
+
+#define POPUP_SPECIFIC_FOLDER_ID_STR "popup"
+
+void notification_popup_msg(MsgInfo*);
+
+#endif /* NOTIFICATION_POPUP */
+
+#endif /* NOTIFICATION_POPUP_H */
diff --git a/src/plugins/notification/notification_prefs.c b/src/plugins/notification/notification_prefs.c
new file mode 100644 (file)
index 0000000..28a519a
--- /dev/null
@@ -0,0 +1,1952 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pluginconfig.h"
+
+#include <math.h>
+
+#include "prefs_gtk.h"
+#include "common/prefs.h"
+#include "common/utils.h"
+#include "common/defs.h"
+#include "gtk/gtkutils.h"
+#include "main.h"
+
+#include "notification_prefs.h"
+#include "notification_plugin.h"
+#include "notification_core.h"
+
+#include "notification_popup.h"
+#include "notification_command.h"
+#include "notification_lcdproc.h"
+#include "notification_trayicon.h"
+#include "notification_indicator.h"
+#include "notification_hotkeys.h"
+
+#include "notification_foldercheck.h"
+
+#ifdef HAVE_LIBNOTIFY
+/* allow infinite timeout with libnotify */
+# define TIMEOUT_SPINNER_MIN     0.0
+#else
+/* don't allow infinite timeout with self-handled popups */
+# define TIMEOUT_SPINNER_MIN     0.2
+#endif
+
+#define TIMEOUT_SPINNER_MAX  3600.0
+#define TIMEOUT_SPINNER_STEP    0.5
+
+typedef struct
+{
+       PrefsPage page;
+       GtkWidget *include_mail;
+       GtkWidget *include_news;
+       GtkWidget *include_rss;
+       GtkWidget *include_calendar;
+       GtkWidget *urgency_hint_new;
+       GtkWidget *urgency_hint_unread;
+#ifdef HAVE_LIBCANBERRA_GTK
+       GtkWidget *canberra_play_sounds;
+#endif
+} NotifyPage;
+
+NotifyPrefs notify_config;
+NotifyPage notify_page;
+
+#ifdef NOTIFICATION_BANNER
+typedef struct {
+       PrefsPage page;
+       GtkWidget *banner_show;
+       GtkWidget *banner_speed;
+       GtkWidget *banner_width;
+       GtkWidget *banner_include_unread;
+       GtkWidget *banner_max_msgs;
+       GtkWidget *banner_sticky;
+       GtkWidget *banner_folder_specific;
+       GtkWidget *banner_enable_colors;
+       GtkWidget *banner_color_bg;
+       GtkWidget *banner_color_fg;
+       GtkWidget *banner_cont_enable;
+       GtkWidget *banner_cont_folder_specific;
+       GtkWidget *banner_cont_color_sel;
+}NotifyBannerPage;
+NotifyBannerPage banner_page;
+#endif /* NOTIFICATION_BANNER */
+
+#ifdef NOTIFICATION_POPUP
+typedef struct {
+       PrefsPage page;
+       GtkWidget *popup_show;
+       GtkWidget *popup_timeout;
+       GtkWidget *popup_folder_specific;
+       GtkWidget *popup_cont_enable;
+       GtkWidget *popup_cont_folder_specific;
+#ifndef HAVE_LIBNOTIFY
+       GtkWidget *popup_sticky;
+       GtkWidget *popup_enable_colors;
+       GtkWidget *popup_color_bg;
+       GtkWidget *popup_color_fg;
+       GtkWidget *popup_cont_color_sel;
+#else /* HAVE_LIBNOTIFY */
+       GtkWidget *popup_display_folder_name;
+#endif /* HAVE_LIBNOTIFY */
+}NotifyPopupPage;
+NotifyPopupPage popup_page;
+#endif /* NOTIFICATION_POPUP */
+
+#ifdef NOTIFICATION_COMMAND
+typedef struct {
+       PrefsPage page;
+       GtkWidget *command_enabled;
+       GtkWidget *command_timeout;
+       GtkWidget *command_folder_specific;
+       GtkWidget *command_line;
+       GtkWidget *command_cont_enable;
+       GtkWidget *command_cont_folder_specific;
+}NotifyCommandPage;
+NotifyCommandPage command_page;
+#endif /* NOTIFICATION_COMMAND */
+
+#ifdef NOTIFICATION_LCDPROC
+typedef struct {
+       PrefsPage page;
+       GtkWidget *lcdproc_enabled;
+       GtkWidget *lcdproc_cont_enable;
+       GtkWidget *lcdproc_hostname;
+       GtkWidget *lcdproc_port;
+}NotifyLCDProcPage;
+NotifyLCDProcPage lcdproc_page;
+#endif
+
+#ifdef NOTIFICATION_TRAYICON
+typedef struct {
+       PrefsPage page;
+       GtkWidget *trayicon_enabled;
+       GtkWidget *trayicon_cont_enable;
+       GtkWidget *trayicon_hide_at_startup;
+       GtkWidget *trayicon_close_to_tray;
+       GtkWidget *trayicon_hide_when_iconified;
+       GtkWidget *trayicon_folder_specific;
+       GtkWidget *trayicon_cont_folder_specific;
+#ifdef HAVE_LIBNOTIFY
+       GtkWidget *trayicon_display_folder_name;
+       GtkWidget *trayicon_popup_enabled;
+       GtkWidget *trayicon_popup_cont_enable;
+       GtkWidget *trayicon_popup_timeout;
+#endif
+}NotifyTrayiconPage;
+NotifyTrayiconPage trayicon_page;
+#endif
+
+#ifdef NOTIFICATION_INDICATOR
+typedef struct {
+       PrefsPage page;
+       GtkWidget *indicator_enabled;
+       GtkWidget *indicator_cont_enable;
+       GtkWidget *indicator_register;
+       GtkWidget *indicator_hide_minimized;
+}NotifyIndicatorPage;
+NotifyIndicatorPage indicator_page;
+#endif
+
+#ifdef NOTIFICATION_HOTKEYS
+typedef struct {
+    PrefsPage page;
+    GtkWidget *hotkeys_enabled;
+    GtkWidget *hotkeys_cont_enable;
+    GtkWidget *hotkeys_toggle_mainwindow;
+}NotifyHotkeysPage;
+NotifyHotkeysPage hotkeys_page;
+#endif
+
+
+PrefParam
+               notify_param[] =
+               {
+
+                               { "include_mail", "TRUE", &notify_config.include_mail, P_BOOL, NULL,
+                                               NULL, NULL },
+                               { "include_news", "TRUE", &notify_config.include_news, P_BOOL, NULL,
+                                               NULL, NULL },
+                               { "include_rss", "TRUE", &notify_config.include_rss, P_BOOL, NULL,
+                                               NULL, NULL },
+                               { "include_calendar", "TRUE", &notify_config.include_calendar, P_BOOL,
+                                               NULL, NULL, NULL },
+                               { "urgency_hint_new", "FALSE", &notify_config.urgency_hint_new, P_BOOL,
+                                               NULL, NULL, NULL },
+                               { "urgency_hint_unread", "FALSE", &notify_config.urgency_hint_unread, P_BOOL,
+                                               NULL, NULL, NULL },
+
+#ifdef HAVE_LIBCANBERRA_GTK
+                { "canberra_play_sounds", "TRUE", &notify_config.canberra_play_sounds, P_BOOL,
+                        NULL, NULL, NULL },
+#endif
+
+#ifdef NOTIFICATION_BANNER
+                               {       "banner_show", "0", &notify_config.banner_show, P_INT, NULL, NULL, NULL},
+                               {       "banner_speed", "30", &notify_config.banner_speed, P_INT, NULL, NULL, NULL},
+                               {       "banner_width", "0", &notify_config.banner_width, P_INT, NULL, NULL, NULL},
+                               {       "banner_include_unread", "FALSE", &notify_config.banner_include_unread,
+                                       P_BOOL, NULL, NULL, NULL},
+                               {       "banner_max_msgs", "100", &notify_config.banner_max_msgs, P_INT,
+                                       NULL, NULL, NULL},
+                               {       "banner_sticky", "FALSE", &notify_config.banner_sticky,
+                                       P_BOOL, NULL, NULL, NULL},
+                               {       "banner_root_x", "0", &notify_config.banner_root_x, P_INT,
+                                       NULL, NULL, NULL},
+                               {       "banner_root_y", "10", &notify_config.banner_root_y, P_INT,
+                                       NULL, NULL, NULL},
+                               {       "banner_folder_specific", "FALSE", &notify_config.banner_folder_specific,
+                                       P_BOOL, NULL, NULL, NULL},
+                               {       "banner_enable_colors", "FALSE", &notify_config.banner_enable_colors,
+                                       P_BOOL, NULL, NULL, NULL},
+                               {       "banner_color_bg", "0", &notify_config.banner_color_bg, P_COLOR,
+                                       NULL, NULL, NULL},
+                               {       "banner_color_fg", "16776704", &notify_config.banner_color_fg, P_COLOR,
+                                       NULL, NULL, NULL},
+#endif
+
+#ifdef NOTIFICATION_POPUP
+                               {       "popup_show", "TRUE", &notify_config.popup_show, P_BOOL, NULL, NULL, NULL},
+                               {       "popup_timeout", "5000", &notify_config.popup_timeout,
+                                       P_INT, NULL, NULL, NULL},
+                               {       "popup_folder_specific", "FALSE", &notify_config.popup_folder_specific,
+                                       P_BOOL, NULL, NULL, NULL},
+#ifndef HAVE_LIBNOTIFY
+                               {       "popup_sticky", "TRUE", &notify_config.popup_sticky, P_BOOL,
+                                       NULL, NULL, NULL},
+                               {       "popup_root_x", "10", &notify_config.popup_root_x,
+                                       P_INT, NULL, NULL, NULL},
+                               {       "popup_root_y", "10", &notify_config.popup_root_y,
+                                       P_INT, NULL, NULL, NULL},
+                               {       "popup_width", "100", &notify_config.popup_width,
+                                       P_INT, NULL, NULL, NULL},
+                               {       "popup_enable_colors", "FALSE", &notify_config.popup_enable_colors, P_BOOL,
+                                       NULL, NULL, NULL},
+                               {       "popup_color_bg", "0", &notify_config.popup_color_bg, P_COLOR,
+                                       NULL, NULL, NULL},
+                               {       "popup_color_fg", "16776704", &notify_config.popup_color_fg, P_COLOR,
+                                       NULL, NULL, NULL},
+#else /* HAVE_LIBNOTIFY */
+                               {       "popup_display_folder_name", "FALSE", &notify_config.popup_display_folder_name,
+                                       P_BOOL, NULL, NULL, NULL},
+#endif /* HAVE_LIBNOTIFY */
+#endif
+
+#ifdef NOTIFICATION_COMMAND
+                               {       "command_enabled", "FALSE", &notify_config.command_enabled, P_BOOL,
+                                       NULL, NULL, NULL},
+                               {       "command_timeout", "60000", &notify_config.command_timeout, P_INT,
+                                       NULL, NULL, NULL},
+                               {       "command_folder_specific", "FALSE", &notify_config.command_folder_specific,
+                                       P_BOOL, NULL, NULL, NULL},
+                               {       "command_line", "", &notify_config.command_line, P_STRING,
+                                       NULL, NULL, NULL},
+#endif
+
+#ifdef NOTIFICATION_LCDPROC
+                               {       "lcdproc_enabled", "FALSE", &notify_config.lcdproc_enabled, P_BOOL,
+                                       NULL, NULL, NULL},
+                               {       "lcdproc_hostname", "localhost", &notify_config.lcdproc_hostname, P_STRING,
+                                       NULL, NULL, NULL},
+                               {       "lcdproc_port", "13666", &notify_config.lcdproc_port, P_INT,
+                                       NULL, NULL, NULL},
+#endif
+
+#ifdef NOTIFICATION_TRAYICON
+#ifndef G_OS_WIN32
+                               {       "trayicon_enabled", "FALSE", &notify_config.trayicon_enabled, P_BOOL,
+                                       NULL, NULL, NULL},
+#else
+                               {       "trayicon_enabled", "TRUE", &notify_config.trayicon_enabled, P_BOOL,
+                                       NULL, NULL, NULL},
+#endif
+                               {       "trayicon_hide_at_startup", "FALSE",
+                                       &notify_config.trayicon_hide_at_startup, P_BOOL, NULL, NULL, NULL},
+                               {       "trayicon_close_to_tray", "FALSE",
+                                       &notify_config.trayicon_close_to_tray, P_BOOL, NULL, NULL, NULL},
+                               {       "trayicon_hide_when_iconified", "FALSE",
+                                       &notify_config.trayicon_hide_when_iconified, P_BOOL, NULL, NULL, NULL},
+                               {       "trayicon_folder_specific", "FALSE",
+                                       &notify_config.trayicon_folder_specific,
+                                       P_BOOL, NULL, NULL, NULL},
+#ifdef HAVE_LIBNOTIFY
+                               {       "trayicon_display_folder_name", "FALSE",
+                                       &notify_config.trayicon_display_folder_name,
+                                       P_BOOL, NULL, NULL, NULL},
+                               {       "trayicon_popup_enabled", "TRUE", &notify_config.trayicon_popup_enabled,
+                                       P_BOOL, NULL, NULL, NULL},
+                               {       "trayicon_popup_timeout", "5000", &notify_config.trayicon_popup_timeout,
+                                       P_INT, NULL, NULL, NULL},
+#endif /* HAVE_LIBNOTIFY */
+#endif
+
+#ifdef NOTIFICATION_INDICATOR
+                               {       "indicator_enabled", "FALSE", &notify_config.indicator_enabled, P_BOOL,
+                                       NULL, NULL, NULL},
+                {   "indicator_hide_minimized", "FALSE", &notify_config.indicator_hide_minimized, P_BOOL,
+                    NULL, NULL, NULL},
+#endif /* NOTIFICATION_INDICATOR */
+#ifdef NOTIFICATION_HOTKEYS
+                {   "hotkeys_enabled", "FALSE", &notify_config.hotkeys_enabled, P_BOOL,
+                    NULL, NULL, NULL},
+                {   "hotkeys_toggle_mainwindow", "", &notify_config.hotkeys_toggle_mainwindow,
+                    P_STRING, NULL, NULL, NULL},
+#endif /* NOTIFICATION_HOTKEYS */
+                               { NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL } };
+
+static void notify_create_prefs_page(PrefsPage*, GtkWindow*, gpointer);
+static void notify_destroy_prefs_page(PrefsPage*);
+static void notify_save_prefs(PrefsPage*);
+
+#ifdef NOTIFICATION_BANNER
+static void notify_create_banner_page(PrefsPage*, GtkWindow*, gpointer);
+static void notify_destroy_banner_page(PrefsPage*);
+static void notify_save_banner(PrefsPage*);
+static void notify_banner_enable_set_sensitivity(GtkComboBox*, gpointer);
+static void notify_banner_color_sel_set_sensitivity(GtkToggleButton*,gpointer);
+static void notify_banner_folder_specific_set_sensitivity(GtkToggleButton*,
+               gpointer);
+#endif
+
+#ifdef NOTIFICATION_POPUP
+static void notify_create_popup_page(PrefsPage*, GtkWindow*, gpointer);
+static void notify_destroy_popup_page(PrefsPage*);
+static void notify_save_popup(PrefsPage*);
+static void notify_popup_folder_specific_set_sensitivity(GtkToggleButton*,
+               gpointer);
+static void notify_popup_enable_set_sensitivity(GtkToggleButton*, gpointer);
+#ifndef HAVE_LIBNOTIFY
+static void notify_popup_set_done_cb(GtkWidget*, gpointer);
+static void notify_popup_set_cb(GtkWidget*, gpointer);
+static void notify_popup_color_sel_set_sensitivity(GtkToggleButton*,gpointer);
+#endif /* !HAVE_LIBNOTIFY */
+#endif
+
+#ifdef NOTIFICATION_COMMAND
+static void notify_create_command_page(PrefsPage*, GtkWindow*, gpointer);
+static void notify_destroy_command_page(PrefsPage*);
+static void notify_save_command(PrefsPage*);
+static void notify_command_enable_set_sensitivity(GtkToggleButton*, gpointer);
+static void notify_command_folder_specific_set_sensitivity(GtkToggleButton*,
+               gpointer);
+#endif
+
+#ifdef NOTIFICATION_LCDPROC
+static void notify_create_lcdproc_page(PrefsPage*, GtkWindow*, gpointer);
+static void notify_destroy_lcdproc_page(PrefsPage*);
+static void notify_save_lcdproc(PrefsPage*);
+static void notify_lcdproc_enable_set_sensitivity(GtkToggleButton*, gpointer);
+#endif
+
+#ifdef NOTIFICATION_TRAYICON
+static void notify_create_trayicon_page(PrefsPage*, GtkWindow*, gpointer);
+static void notify_destroy_trayicon_page(PrefsPage*);
+static void notify_save_trayicon(PrefsPage*);
+static void notify_trayicon_enable_set_sensitivity(GtkToggleButton*, gpointer);
+static void notify_trayicon_folder_specific_set_sensitivity(GtkToggleButton*,
+               gpointer);
+#ifdef HAVE_LIBNOTIFY
+static void notify_trayicon_popup_enable_set_sensitivity(GtkToggleButton*,
+               gpointer);
+#endif
+#endif
+
+#ifdef NOTIFICATION_INDICATOR
+static void notify_create_indicator_page(PrefsPage*, GtkWindow*, gpointer);
+static void notify_destroy_indicator_page(PrefsPage*);
+static void notify_save_indicator(PrefsPage*);
+static void notify_indicator_enable_set_sensitivity(GtkToggleButton*, gpointer);
+#endif /* NOTIFICATION_INDICATOR */
+
+#ifdef NOTIFICATION_HOTKEYS
+static void notify_create_hotkeys_page(PrefsPage*, GtkWindow*, gpointer);
+static void notify_destroy_hotkeys_page(PrefsPage*);
+static void notify_save_hotkeys(PrefsPage*);
+static void notify_hotkeys_enable_set_sensitivity(GtkToggleButton*, gpointer);
+#endif /* NOTIFICATION_HOTKEYS */
+
+
+static gint conv_color_to_int(GdkColor*);
+
+void notify_gtk_init(void)
+{
+       static gchar *path[3];
+
+       path[0] = D_("claws-mail", "Plugins");
+       path[1] = _("Notification");
+       path[2] = NULL;
+
+       notify_page.page.path = path;
+       notify_page.page.create_widget = notify_create_prefs_page;
+       notify_page.page.destroy_widget = notify_destroy_prefs_page;
+       notify_page.page.save_page = notify_save_prefs;
+       prefs_gtk_register_page((PrefsPage*) &notify_page);
+
+#ifdef NOTIFICATION_HOTKEYS
+    {
+        static gchar *hotkeys_path[4];
+
+        hotkeys_path[0] = D_("claws-mail", "Plugins");
+        hotkeys_path[1] = _("Notification");
+        hotkeys_path[2] = _("Hotkeys");
+        hotkeys_path[3] = NULL;
+
+        hotkeys_page.page.path = hotkeys_path;
+        hotkeys_page.page.create_widget = notify_create_hotkeys_page;
+        hotkeys_page.page.destroy_widget = notify_destroy_hotkeys_page;
+        hotkeys_page.page.save_page = notify_save_hotkeys;
+        prefs_gtk_register_page((PrefsPage*) &hotkeys_page);
+    }
+#endif /* NOTIFICATION_HOTKEYS */
+
+
+#ifdef NOTIFICATION_BANNER
+       {
+               static gchar *banner_path[4];
+
+               banner_path[0] = D_("claws-mail", "Plugins");
+               banner_path[1] = _("Notification");
+               banner_path[2] = _("Banner");
+               banner_path[3] = NULL;
+
+               banner_page.page.path = banner_path;
+               banner_page.page.create_widget = notify_create_banner_page;
+               banner_page.page.destroy_widget = notify_destroy_banner_page;
+               banner_page.page.save_page = notify_save_banner;
+               prefs_gtk_register_page((PrefsPage*) &banner_page);
+       }
+#endif /* NOTIFICATION_BANNER */
+
+#ifdef NOTIFICATION_POPUP
+       {
+               static gchar *popup_path[4];
+
+               popup_path[0] = D_("claws-mail", "Plugins");
+               popup_path[1] = _("Notification");
+               popup_path[2] = _("Popup");
+               popup_path[3] = NULL;
+
+               popup_page.page.path = popup_path;
+               popup_page.page.create_widget = notify_create_popup_page;
+               popup_page.page.destroy_widget = notify_destroy_popup_page;
+               popup_page.page.save_page = notify_save_popup;
+               prefs_gtk_register_page((PrefsPage*) &popup_page);
+       }
+#endif /* NOTIFICATION_POPUP */
+
+#ifdef NOTIFICATION_COMMAND
+       {
+               static gchar *command_path[4];
+
+               command_path[0] = D_("claws-mail", "Plugins");
+               command_path[1] = _("Notification");
+               command_path[2] = _("Command");
+               command_path[3] = NULL;
+
+               command_page.page.path = command_path;
+               command_page.page.create_widget = notify_create_command_page;
+               command_page.page.destroy_widget = notify_destroy_command_page;
+               command_page.page.save_page = notify_save_command;
+               prefs_gtk_register_page((PrefsPage*) &command_page);
+       }
+#endif /* NOTIFICATION_COMMAND */
+
+#ifdef NOTIFICATION_LCDPROC
+       {
+               static gchar *lcdproc_path[4];
+
+               lcdproc_path[0] = D_("claws-mail", "Plugins");
+               lcdproc_path[1] = _("Notification");
+               lcdproc_path[2] = _("LCD");
+               lcdproc_path[3] = NULL;
+
+               lcdproc_page.page.path = lcdproc_path;
+               lcdproc_page.page.create_widget = notify_create_lcdproc_page;
+               lcdproc_page.page.destroy_widget = notify_destroy_lcdproc_page;
+               lcdproc_page.page.save_page = notify_save_lcdproc;
+               prefs_gtk_register_page((PrefsPage*) &lcdproc_page);
+       }
+#endif /* NOTIFICATION_LCDPROC */
+
+#ifdef NOTIFICATION_TRAYICON
+       {
+               static gchar *trayicon_path[4];
+
+               trayicon_path[0] = D_("claws-mail", "Plugins");
+               trayicon_path[1] = _("Notification");
+               trayicon_path[2] = _("SysTrayicon");
+               trayicon_path[3] = NULL;
+
+               trayicon_page.page.path = trayicon_path;
+               trayicon_page.page.create_widget = notify_create_trayicon_page;
+               trayicon_page.page.destroy_widget = notify_destroy_trayicon_page;
+               trayicon_page.page.save_page = notify_save_trayicon;
+               prefs_gtk_register_page((PrefsPage*) &trayicon_page);
+       }
+#endif /* NOTIFICATION_TRAYICON */
+
+#ifdef NOTIFICATION_INDICATOR
+       {
+               static gchar *indicator_path[4];
+
+               indicator_path[0] = D_("claws-mail", "Plugins");
+               indicator_path[1] = _("Notification");
+               indicator_path[2] = _("Indicator");
+               indicator_path[3] = NULL;
+
+               indicator_page.page.path = indicator_path;
+               indicator_page.page.create_widget = notify_create_indicator_page;
+               indicator_page.page.destroy_widget = notify_destroy_indicator_page;
+               indicator_page.page.save_page = notify_save_indicator;
+               prefs_gtk_register_page((PrefsPage*) &indicator_page);
+       }
+#endif /* NOTIFICATION_INDICATOR */
+}
+
+void notify_gtk_done(void)
+{
+       if (claws_is_exiting())
+               return;
+       prefs_gtk_unregister_page((PrefsPage*) &notify_page);
+#ifdef NOTIFICATION_BANNER
+       prefs_gtk_unregister_page((PrefsPage*) &banner_page);
+#endif
+#ifdef NOTIFICATION_POPUP
+       prefs_gtk_unregister_page((PrefsPage*) &popup_page);
+#endif
+#ifdef NOTIFICATION_COMMAND
+       prefs_gtk_unregister_page((PrefsPage*) &command_page);
+#endif
+#ifdef NOTIFICATION_LCDPROC
+       prefs_gtk_unregister_page((PrefsPage*) &lcdproc_page);
+#endif
+#ifdef NOTIFICATION_TRAYICON
+       prefs_gtk_unregister_page((PrefsPage*) &trayicon_page);
+#endif
+#ifdef NOTIFICATION_INDICATOR
+       prefs_gtk_unregister_page((PrefsPage*) &indicator_page);
+#endif
+#ifdef NOTIFICATION_HOTKEYS
+       prefs_gtk_unregister_page((PrefsPage*) &hotkeys_page);
+#endif
+}
+
+void notify_save_config(void)
+{
+       PrefFile *pfile;
+       gchar *rcpath;
+
+       debug_print("Saving Notification plugin configuration...\n");
+
+       rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+       pfile = prefs_write_open(rcpath);
+       g_free(rcpath);
+       if (!pfile || (prefs_set_block_label(pfile, "NotificationPlugin") < 0))
+               return;
+
+       if (prefs_write_param(notify_param, pfile->fp) < 0) {
+               debug_print("failed!\n");
+               g_warning(_("\nNotification Plugin: Failed to write plugin configuration "
+                                               "to file\n"));
+               prefs_file_close_revert(pfile);
+               return;
+       }
+       if (fprintf(pfile->fp, "\n") < 0) {
+               FILE_OP_ERROR(rcpath, "fprintf");
+               prefs_file_close_revert(pfile);
+       }
+       else
+               prefs_file_close(pfile);
+       debug_print("done.\n");
+}
+
+static void notify_create_prefs_page(PrefsPage *page, GtkWindow *window,
+               gpointer data)
+{
+       GtkWidget *pvbox;
+       GtkWidget *vbox;
+       GtkWidget *checkbox;
+       GtkWidget *frame;
+       GtkWidget *label;
+
+       /* Page vbox */
+       pvbox = gtk_vbox_new(FALSE, 0);
+
+       /* Frame */
+       frame = gtk_frame_new(_("Include folder types"));
+       gtk_container_set_border_width(GTK_CONTAINER(frame), 10);
+       gtk_box_pack_start(GTK_BOX(pvbox), frame, FALSE, FALSE, 0);
+
+       /* Frame vbox */
+       vbox = gtk_vbox_new(FALSE, 4);
+       gtk_container_add(GTK_CONTAINER(frame), vbox);
+       gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
+
+       /* Include mail folders */
+       checkbox = gtk_check_button_new_with_label(_("Mail folders"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+       notify_config.include_mail);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       notify_page.include_mail = checkbox;
+
+       /* Include news folders */
+       checkbox = gtk_check_button_new_with_label(_("News folders"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+       notify_config.include_news);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       notify_page.include_news = checkbox;
+
+       /* Include RSS folders */
+       checkbox = gtk_check_button_new_with_label(_("RSS folders"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+       notify_config.include_rss);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       notify_page.include_rss = checkbox;
+
+       /* Include calendar folders */
+       checkbox = gtk_check_button_new_with_label(_("Calendar folders"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.include_calendar);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       notify_page.include_calendar = checkbox;
+
+       /* Warning-Label */
+       label = gtk_label_new(_("These settings overwrite folder-specific "
+                       "selections."));
+       gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
+       gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+       gtk_widget_show(label);
+
+       /* done with the frame */
+       gtk_widget_show(frame);
+       gtk_widget_show(vbox);
+
+       /* Frame */
+       frame = gtk_frame_new(_("Global notification settings"));
+       gtk_container_set_border_width(GTK_CONTAINER(frame), 10);
+       gtk_box_pack_start(GTK_BOX(pvbox), frame, FALSE, FALSE, 0);
+
+       /* Frame vbox */
+       vbox = gtk_vbox_new(FALSE, 4);
+       gtk_container_add(GTK_CONTAINER(frame), vbox);
+       gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
+
+       /* urgency hint new */
+       checkbox = gtk_check_button_new_with_label(_("Set window manager "
+                       "urgency hint when new messages exist"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.urgency_hint_new);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       notify_page.urgency_hint_new = checkbox;
+
+       /* urgency hint new */
+       checkbox = gtk_check_button_new_with_label(_("Set window manager "
+                       "urgency hint when unread messages exist"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.urgency_hint_unread);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       notify_page.urgency_hint_unread = checkbox;
+
+#ifdef HAVE_LIBCANBERRA_GTK
+       /* canberra */
+    checkbox = gtk_check_button_new_with_label(_("Use sound theme"));
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+            notify_config.canberra_play_sounds);
+    gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+    gtk_widget_show(checkbox);
+    notify_page.canberra_play_sounds = checkbox;
+#endif
+
+       /* done with the frame */
+       gtk_widget_show(frame);
+       gtk_widget_show(vbox);
+
+       /* done with the page */
+       gtk_widget_show(pvbox);
+       page->widget = pvbox;
+}
+
+static void notify_destroy_prefs_page(PrefsPage *page)
+{
+}
+
+static void notify_save_prefs(PrefsPage *page)
+{
+       notify_config.include_mail =gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(notify_page.include_mail));
+       notify_config.include_news =gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(notify_page.include_news));
+       notify_config.include_rss =gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(notify_page.include_rss));
+       notify_config.include_calendar
+                       = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(notify_page.include_calendar));
+       notify_config.urgency_hint_new = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(notify_page.urgency_hint_new));
+       notify_config.urgency_hint_unread = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(notify_page.urgency_hint_unread));
+#ifdef HAVE_LIBCANBERRA_GTK
+   notify_config.canberra_play_sounds = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(notify_page.canberra_play_sounds));
+#endif
+
+   notification_core_global_includes_changed();
+}
+
+#ifdef NOTIFICATION_BANNER
+static void notify_create_banner_page(PrefsPage *page, GtkWindow *window,
+               gpointer data)
+{
+       GtkRequisition requisition;
+       GtkWidget *pvbox;
+       GtkWidget *vbox;
+       GtkWidget *hbox;
+       GtkWidget *table;
+       GtkWidget *checkbox;
+       GtkWidget *button;
+       GtkWidget *combo;
+       GtkWidget *spinner;
+       GtkWidget *label;
+       GtkWidget *slider;
+       GtkWidget *color_sel;
+       GdkColor bg;
+       GdkColor fg;
+
+       pvbox = gtk_vbox_new(FALSE, 20);
+       gtk_container_set_border_width(GTK_CONTAINER(pvbox), 10);
+
+       /* Always / Never / Only when non-empty */
+       hbox = gtk_hbox_new(FALSE, 20);
+       gtk_box_pack_start(GTK_BOX(pvbox), hbox, FALSE, FALSE, 0);
+       label = gtk_label_new(_("Show banner"));
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       gtk_widget_show(label);
+       combo = gtk_combo_box_new_text();
+       gtk_combo_box_insert_text(GTK_COMBO_BOX(combo), NOTIFY_BANNER_SHOW_NEVER,
+                       _("Never"));
+       gtk_combo_box_insert_text(GTK_COMBO_BOX(combo), NOTIFY_BANNER_SHOW_ALWAYS,
+                       _("Always"));
+       gtk_combo_box_insert_text(GTK_COMBO_BOX(combo), NOTIFY_BANNER_SHOW_NONEMPTY,
+                       _("Only when not empty"));
+       gtk_combo_box_set_active(GTK_COMBO_BOX(combo), notify_config.banner_show);
+       gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(combo), "changed",
+                       G_CALLBACK(notify_banner_enable_set_sensitivity), NULL);
+       gtk_widget_show(combo);
+       gtk_widget_show(hbox);
+       banner_page.banner_show = combo;
+
+       /* Container vbox for greying out everything */
+       vbox = gtk_vbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(pvbox), vbox, FALSE, FALSE, 0);
+       gtk_widget_show(vbox);
+       banner_page.banner_cont_enable = vbox;
+
+       /* Banner speed */
+       table = gtk_table_new(2, 3, FALSE);
+       gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
+       label = gtk_label_new(_("slow"));
+       gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 0, 1);
+       gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+       gtk_widget_show(label);
+       label = gtk_label_new(_("fast"));
+       gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, 0, 1);
+       gtk_misc_set_alignment(GTK_MISC(label), 1, 0);
+       gtk_widget_show(label);
+       label = gtk_label_new(_("Banner speed"));
+       gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
+       gtk_widget_show(label);
+       slider = gtk_hscale_new_with_range(10., 70., 10.);
+       gtk_scale_set_digits(GTK_SCALE(slider), 0);
+       gtk_widget_size_request(combo, &requisition);
+       gtk_widget_set_size_request(slider, requisition.width, -1);
+       gtk_range_set_increments(GTK_RANGE(slider), 10., 10.);
+       gtk_range_set_inverted(GTK_RANGE(slider), TRUE);
+       gtk_scale_set_draw_value(GTK_SCALE(slider), FALSE);
+       gtk_range_set_value(GTK_RANGE(slider), notify_config.banner_speed);
+       gtk_table_attach_defaults(GTK_TABLE(table), slider, 1, 3, 1, 2);
+       gtk_widget_show(slider);
+       gtk_widget_show(table);
+       banner_page.banner_speed = slider;
+
+       /* Maximum number of messages in banner */
+       hbox = gtk_hbox_new(FALSE, 10);
+       label = gtk_label_new(_("Maximum number of messages (0 means unlimited)"));
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       gtk_widget_show(label);
+       spinner = gtk_spin_button_new_with_range(0., 1000., 1.);
+       gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinner), 0);
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinner), notify_config.banner_max_msgs);
+       gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       gtk_widget_show(spinner);
+       gtk_widget_show(hbox);
+       banner_page.banner_max_msgs = spinner;
+
+       /* banner width */
+       hbox = gtk_hbox_new(FALSE, 10);
+       label = gtk_label_new(_("Banner width in pixels (0 means screen size)"));
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       gtk_widget_show(label);
+       spinner = gtk_spin_button_new_with_range(0.,5000., 50);
+       gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinner),0);
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinner),notify_config.banner_width);
+       gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       gtk_widget_show(spinner);
+       gtk_widget_show(hbox);
+       banner_page.banner_width = spinner;
+
+       /* Include unread */
+       checkbox = gtk_check_button_new_with_label(_("Include unread mails in banner"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.banner_include_unread);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       banner_page.banner_include_unread = checkbox;
+
+       /* Check button sticky */
+       checkbox = gtk_check_button_new_with_label(_("Make banner sticky"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.banner_sticky);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       banner_page.banner_sticky = checkbox;
+
+       /* Check box for enabling folder specific selection */
+       hbox = gtk_hbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       checkbox = gtk_check_button_new_with_label(_("Only include selected folders"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.banner_folder_specific);
+       gtk_box_pack_start(GTK_BOX(hbox), checkbox, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(checkbox), "toggled",
+                       G_CALLBACK(notify_banner_folder_specific_set_sensitivity),
+                       NULL);
+       gtk_widget_show(checkbox);
+       banner_page.banner_folder_specific = checkbox;
+       button = gtk_button_new_with_label(_("Select folders..."));
+       g_signal_connect(G_OBJECT(button), "clicked",
+                       G_CALLBACK(notification_foldercheck_sel_folders_cb),
+                       BANNER_SPECIFIC_FOLDER_ID_STR);
+       gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+       banner_page.banner_cont_folder_specific = button;
+       gtk_widget_show(button);
+       gtk_widget_show(hbox);
+
+       /* Check box for enabling custom colors */
+       checkbox = gtk_check_button_new_with_label(_("Use custom colors"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.banner_enable_colors);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(checkbox), "toggled",
+                       G_CALLBACK(notify_banner_color_sel_set_sensitivity), NULL);
+       gtk_widget_show(checkbox);
+       banner_page.banner_enable_colors = checkbox;
+
+       /* Color selection dialogs for foreground and background color */
+       /* foreground */
+       table = gtk_table_new(2, 2, FALSE);
+       gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
+       label = gtk_label_new(_("Foreground"));
+       gtk_table_attach_defaults(GTK_TABLE(table),label,0,1,0,1);
+       gtk_widget_show(label);
+       color_sel = gtk_color_button_new();
+       gtkut_convert_int_to_gdk_color(notify_config.banner_color_fg,&fg);
+       gtk_color_button_set_color(GTK_COLOR_BUTTON(color_sel),&fg);
+       gtk_color_button_set_title(GTK_COLOR_BUTTON(color_sel),_("Foreground color"));
+       gtk_table_attach_defaults(GTK_TABLE(table),color_sel,1,2,0,1);
+       gtk_widget_show(color_sel);
+       banner_page.banner_color_fg = color_sel;
+       /* background */
+       label = gtk_label_new(_("Background"));
+       gtk_table_attach_defaults(GTK_TABLE(table),label,0,1,1,2);
+       gtk_widget_show(label);
+       color_sel = gtk_color_button_new();
+       gtkut_convert_int_to_gdk_color(notify_config.banner_color_bg,&bg);
+       gtk_color_button_set_color(GTK_COLOR_BUTTON(color_sel),&bg);
+       gtk_color_button_set_title(GTK_COLOR_BUTTON(color_sel), _("Background color"));
+       gtk_table_attach_defaults(GTK_TABLE(table),color_sel,1,2,1,2);
+       gtk_widget_show(color_sel);
+       gtk_widget_show(table);
+       banner_page.banner_color_bg = color_sel;
+       banner_page.banner_cont_color_sel = table;
+
+       notify_banner_color_sel_set_sensitivity
+       (GTK_TOGGLE_BUTTON(banner_page.banner_enable_colors), NULL);
+       notify_banner_folder_specific_set_sensitivity
+       (GTK_TOGGLE_BUTTON(banner_page.banner_folder_specific), NULL);
+       notify_banner_enable_set_sensitivity(GTK_COMBO_BOX(combo), NULL);
+       gtk_widget_show(pvbox);
+       banner_page.page.widget = pvbox;
+}
+
+static void notify_destroy_banner_page(PrefsPage *page)
+{
+}
+
+static void notify_save_banner(PrefsPage *page)
+{
+       gdouble range_val;
+       GdkColor color;
+
+       notify_config.banner_show =
+               gtk_combo_box_get_active(GTK_COMBO_BOX(banner_page.banner_show));
+       notify_config.banner_max_msgs =
+               gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(banner_page.banner_max_msgs));
+       notify_config.banner_width =
+               gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(banner_page.banner_width));
+       notify_config.banner_include_unread =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(banner_page.banner_include_unread));
+       range_val= gtk_range_get_value(GTK_RANGE(banner_page.banner_speed));
+       notify_config.banner_speed = (gint)ceil(range_val);
+
+       notify_config.banner_sticky =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(banner_page.banner_sticky));
+
+       notify_config.banner_folder_specific =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(banner_page.banner_folder_specific));
+
+       notify_config.banner_enable_colors =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(banner_page.banner_enable_colors));
+
+       /* Color dialogs are a bit more complicated */
+       gtk_color_button_get_color(GTK_COLOR_BUTTON(banner_page.banner_color_fg),
+                       &color);
+       notify_config.banner_color_fg = conv_color_to_int(&color);
+       gtk_color_button_get_color(GTK_COLOR_BUTTON(banner_page.banner_color_bg),
+                       &color);
+       notify_config.banner_color_bg = conv_color_to_int(&color);
+
+       notification_banner_destroy();
+       notification_update_banner();
+}
+
+static void notify_banner_enable_set_sensitivity(GtkComboBox *combo,
+               gpointer data)
+{
+       NotifyBannerShow show;
+
+       show = gtk_combo_box_get_active(GTK_COMBO_BOX(banner_page.banner_show));
+       gtk_widget_set_sensitive(banner_page.banner_cont_enable,
+                       (show == NOTIFY_BANNER_SHOW_NEVER) ? FALSE : TRUE);
+}
+
+static void notify_banner_color_sel_set_sensitivity(GtkToggleButton *button,
+               gpointer data)
+{
+       gboolean active;
+       active = gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(banner_page.banner_enable_colors));
+       gtk_widget_set_sensitive(banner_page.banner_cont_color_sel, active);
+}
+
+static void notify_banner_folder_specific_set_sensitivity(GtkToggleButton *bu,
+               gpointer data)
+{
+       gboolean active;
+       active = gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(banner_page.banner_folder_specific));
+       gtk_widget_set_sensitive(banner_page.banner_cont_folder_specific, active);
+}
+#endif /* NOTIFICATION_BANNER */
+
+#ifdef NOTIFICATION_POPUP
+static void notify_create_popup_page(PrefsPage *page, GtkWindow *window,
+               gpointer data)
+{
+       gdouble timeout;
+       GtkWidget *pvbox;
+       GtkWidget *vbox;
+       GtkWidget *checkbox;
+       GtkWidget *hbox;
+       GtkWidget *spinner;
+       GtkWidget *label;
+       GtkWidget *button;
+#ifndef HAVE_LIBNOTIFY
+       GdkColor bg;
+       GdkColor fg;
+       GtkWidget *table;
+       GtkWidget *color_sel;
+#endif /* !HAVE_LIBNOTIFY */
+
+       pvbox = gtk_vbox_new(FALSE, 20);
+       gtk_container_set_border_width(GTK_CONTAINER(pvbox), 10);
+
+       /* Enable popup */
+       checkbox = gtk_check_button_new_with_label(_("Enable popup"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.popup_show);
+       gtk_box_pack_start(GTK_BOX(pvbox), checkbox, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(checkbox), "toggled",
+                       G_CALLBACK(notify_popup_enable_set_sensitivity), NULL);
+       gtk_widget_show(checkbox);
+       popup_page.popup_show = checkbox;
+
+       /* Container vbox for greying out everything */
+       vbox = gtk_vbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(pvbox), vbox, FALSE, FALSE, 0);
+       gtk_widget_show(vbox);
+       popup_page.popup_cont_enable = vbox;
+
+       /* Popup timeout */
+       hbox = gtk_hbox_new(FALSE, 10);
+       label = gtk_label_new(_("Popup timeout:"));
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       gtk_widget_show(label);
+       spinner = gtk_spin_button_new_with_range(TIMEOUT_SPINNER_MIN, TIMEOUT_SPINNER_MAX, TIMEOUT_SPINNER_STEP);
+       gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinner), 1);
+       timeout = notify_config.popup_timeout/1000.;
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinner), timeout);
+       gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       gtk_widget_show(spinner);
+       label = gtk_label_new(_("seconds"));
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       gtk_widget_show(label);
+       gtk_widget_show(hbox);
+       popup_page.popup_timeout = spinner;
+
+       /* Check box for enabling folder specific selection */
+       hbox = gtk_hbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       checkbox = gtk_check_button_new_with_label(_("Only include selected folders"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.popup_folder_specific);
+       gtk_box_pack_start(GTK_BOX(hbox), checkbox, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(checkbox), "toggled",
+                       G_CALLBACK(notify_popup_folder_specific_set_sensitivity),
+                       NULL);
+       gtk_widget_show(checkbox);
+       popup_page.popup_folder_specific = checkbox;
+       button = gtk_button_new_with_label(_("Select folders..."));
+       g_signal_connect(G_OBJECT(button), "clicked",
+                       G_CALLBACK(notification_foldercheck_sel_folders_cb),
+                       POPUP_SPECIFIC_FOLDER_ID_STR);
+       gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+       popup_page.popup_cont_folder_specific = button;
+       gtk_widget_show(button);
+       gtk_widget_show(hbox);
+
+#ifndef HAVE_LIBNOTIFY
+       /* Sticky check button */
+       checkbox = gtk_check_button_new_with_label(_("Make popup sticky"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.popup_sticky);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       popup_page.popup_sticky = checkbox;
+
+       /* Button to set size and position of popup window */
+       hbox = gtk_hbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       button = gtk_button_new_with_label(_("Set popup window width and position"));
+       g_signal_connect(G_OBJECT(button), "clicked",
+                       G_CALLBACK(notify_popup_set_cb), NULL);
+       gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+       gtk_widget_show(button);
+       label = gtk_label_new(_("(the window manager is free to ignore this)"));
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       gtk_widget_show(label);
+       gtk_widget_show(hbox);
+
+       /* Check box for enabling custom colors */
+       checkbox = gtk_check_button_new_with_label(_("Use custom colors"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.popup_enable_colors);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(checkbox), "toggled",
+                       G_CALLBACK(notify_popup_color_sel_set_sensitivity), NULL);
+       gtk_widget_show(checkbox);
+       popup_page.popup_enable_colors = checkbox;
+
+       /* Color selection dialogs for foreground and background color */
+       table = gtk_table_new(2,2,FALSE);
+       gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
+       /* foreground */
+       label = gtk_label_new(_("Foreground"));
+       gtk_table_attach_defaults(GTK_TABLE(table),label,0,1,0,1);
+       gtk_widget_show(label);
+       color_sel = gtk_color_button_new();
+       gtkut_convert_int_to_gdk_color(notify_config.popup_color_fg,&fg);
+       gtk_color_button_set_color(GTK_COLOR_BUTTON(color_sel),&fg);
+       gtk_color_button_set_title(GTK_COLOR_BUTTON(color_sel),_("Foreground color"));
+       gtk_table_attach_defaults(GTK_TABLE(table),color_sel,1,2,0,1);
+       gtk_widget_show(color_sel);
+       popup_page.popup_color_fg = color_sel;
+       /* background */
+       label = gtk_label_new(_("Background"));
+       gtk_table_attach_defaults(GTK_TABLE(table),label,0,1,1,2);
+       gtk_widget_show(label);
+       color_sel = gtk_color_button_new();
+       gtkut_convert_int_to_gdk_color(notify_config.popup_color_bg,&bg);
+       gtk_color_button_set_color(GTK_COLOR_BUTTON(color_sel),&bg);
+       gtk_color_button_set_title(GTK_COLOR_BUTTON(color_sel),_("Background color"));
+       gtk_table_attach_defaults(GTK_TABLE(table),color_sel,1,2,1,2);
+       gtk_widget_show(color_sel);
+       gtk_widget_show(table);
+       popup_page.popup_color_bg = color_sel;
+       popup_page.popup_cont_color_sel = table;
+
+       notify_popup_color_sel_set_sensitivity
+       (GTK_TOGGLE_BUTTON(popup_page.popup_enable_colors), NULL);
+#else /* HAVE_LIBNOTIFY */
+       hbox = gtk_hbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       checkbox = gtk_check_button_new_with_label(_("Display folder name"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.popup_display_folder_name);
+       gtk_box_pack_start(GTK_BOX(hbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       gtk_widget_show(hbox);
+       popup_page.popup_display_folder_name = checkbox;
+#endif /* HAVE_LIBNOTIFY */
+
+       notify_popup_enable_set_sensitivity
+       (GTK_TOGGLE_BUTTON(popup_page.popup_show), NULL);
+       notify_popup_folder_specific_set_sensitivity
+       (GTK_TOGGLE_BUTTON(popup_page.popup_folder_specific), NULL);
+       gtk_widget_show(pvbox);
+       popup_page.page.widget = pvbox;
+}
+
+static void notify_destroy_popup_page(PrefsPage *page)
+{
+}
+
+static void notify_save_popup(PrefsPage *page)
+{
+       gdouble timeout;
+
+#ifndef HAVE_LIBNOTIFY
+       GdkColor color;
+#endif /* !HAVE_LIBNOTIFY */
+
+       notify_config.popup_show =
+       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(popup_page.popup_show));
+       timeout =
+       gtk_spin_button_get_value(GTK_SPIN_BUTTON(popup_page.popup_timeout));
+       notify_config.popup_timeout = (gint)floor(timeout*1000+0.5);
+       notify_config.popup_folder_specific =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(popup_page.popup_folder_specific));
+
+#ifndef HAVE_LIBNOTIFY
+       notify_config.popup_sticky =
+       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(popup_page.popup_sticky));
+       notify_config.popup_enable_colors =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(popup_page.popup_enable_colors));
+
+       /* Color dialogs are a bit more complicated */
+       gtk_color_button_get_color(GTK_COLOR_BUTTON(popup_page.popup_color_fg),
+                       &color);
+       notify_config.popup_color_fg = conv_color_to_int(&color);
+       gtk_color_button_get_color(GTK_COLOR_BUTTON(popup_page.popup_color_bg),
+                       &color);
+       notify_config.popup_color_bg = conv_color_to_int(&color);
+#else /* HAVE_LIBNOTIFY */
+       notify_config.popup_display_folder_name =
+       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(popup_page.popup_display_folder_name));
+#endif /* HAVE_LIBNOTIFY */
+}
+
+#ifndef HAVE_LIBNOTIFY
+static void notify_popup_set_cb(GtkWidget *widget, gpointer data)
+{
+       GtkWidget *win;
+       GtkWidget *button;
+
+       win = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "notification_prefs");
+       gtk_window_set_title(GTK_WINDOW(win), _("Sample popup window"));
+       gtk_window_set_modal(GTK_WINDOW(win), TRUE);
+       gtk_window_set_keep_above(GTK_WINDOW(win), TRUE);
+       gtk_window_set_resizable(GTK_WINDOW(win), TRUE);
+       gtk_container_set_border_width(GTK_CONTAINER(win), 5);
+
+       button = gtk_button_new_with_label(_("Done"));
+       g_signal_connect(G_OBJECT(button), "clicked",
+                       G_CALLBACK(notify_popup_set_done_cb), win);
+       gtk_container_add(GTK_CONTAINER(win), button);
+
+       gtk_widget_show_all(win);
+}
+
+static void notify_popup_set_done_cb(GtkWidget *widget, gpointer data)
+{
+       GtkWidget *win = data;
+       gint dummy;
+
+       gtk_window_get_position(GTK_WINDOW(win),
+                       &(notify_config.popup_root_x),
+                       &(notify_config.popup_root_y));
+       gtk_window_get_size(GTK_WINDOW(win),
+                       &(notify_config.popup_width), &dummy);
+       gtk_widget_destroy(win);
+}
+
+static void notify_popup_color_sel_set_sensitivity(GtkToggleButton *button,
+               gpointer data)
+{
+       gboolean active;
+       active = gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(popup_page.popup_enable_colors));
+       gtk_widget_set_sensitive(popup_page.popup_cont_color_sel, active);
+}
+#endif /* !HAVE_LIBNOTIFY */
+
+static void notify_popup_enable_set_sensitivity(GtkToggleButton *button,
+               gpointer data)
+{
+       gboolean active;
+       active = gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(popup_page.popup_show));
+       gtk_widget_set_sensitive(popup_page.popup_cont_enable, active);
+}
+
+static void notify_popup_folder_specific_set_sensitivity(GtkToggleButton *bu,
+               gpointer data)
+{
+       gboolean active;
+       active = gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(popup_page.popup_folder_specific));
+       gtk_widget_set_sensitive(popup_page.popup_cont_folder_specific, active);
+}
+
+#endif /* NOTIFICATION_POPUP */
+
+#ifdef NOTIFICATION_COMMAND
+static void notify_create_command_page(PrefsPage *page, GtkWindow *window,
+               gpointer data)
+{
+       GtkWidget *pvbox;
+       GtkWidget *vbox;
+       GtkWidget *checkbox;
+       GtkWidget *hbox;
+       GtkWidget *spinner;
+       GtkWidget *entry;
+       GtkWidget *label;
+       GtkWidget *button;
+       gdouble timeout;
+
+       pvbox = gtk_vbox_new(FALSE, 20);
+       gtk_container_set_border_width(GTK_CONTAINER(pvbox), 10);
+
+       /* Enable command */
+       checkbox = gtk_check_button_new_with_label(_("Enable command"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.command_enabled);
+       gtk_box_pack_start(GTK_BOX(pvbox), checkbox, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(checkbox), "toggled",
+                       G_CALLBACK(notify_command_enable_set_sensitivity), NULL);
+       gtk_widget_show(checkbox);
+       command_page.command_enabled = checkbox;
+
+       /* Container vbox for greying out everything */
+       vbox = gtk_vbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(pvbox), vbox, FALSE, FALSE, 0);
+       gtk_widget_show(vbox);
+       command_page.command_cont_enable = vbox;
+
+       /* entry field for command to execute */
+       hbox = gtk_hbox_new(FALSE, 10);
+       label = gtk_label_new(_("Command to execute:"));
+       gtk_widget_show(label);
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       entry = gtk_entry_new();
+       gtk_entry_set_text(GTK_ENTRY(entry), notify_config.command_line);
+       gtk_widget_show(entry);
+       gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 0);
+       gtk_widget_show(hbox);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       command_page.command_line = entry;
+
+       /* Spin button for command timeout */
+       hbox = gtk_hbox_new(FALSE, 10);
+       label = gtk_label_new(_("Block command after execution for"));
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       gtk_widget_show(label);
+       spinner = gtk_spin_button_new_with_range(0., 600., 1.);
+       gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinner), 0);
+       timeout = notify_config.command_timeout/1000.;
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinner), timeout);
+       gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       gtk_widget_show(spinner);
+       label = gtk_label_new(_("seconds"));
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       gtk_widget_show(label);
+       gtk_widget_show(hbox);
+       command_page.command_timeout = spinner;
+
+       /* Check box for enabling folder specific selection */
+       hbox = gtk_hbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       checkbox = gtk_check_button_new_with_label(_("Only include selected folders"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.command_folder_specific);
+       gtk_box_pack_start(GTK_BOX(hbox), checkbox, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(checkbox), "toggled",
+                       G_CALLBACK(notify_command_folder_specific_set_sensitivity),
+                       NULL);
+       gtk_widget_show(checkbox);
+       command_page.command_folder_specific = checkbox;
+       button = gtk_button_new_with_label(_("Select folders..."));
+       g_signal_connect(G_OBJECT(button), "clicked",
+                       G_CALLBACK(notification_foldercheck_sel_folders_cb),
+                       COMMAND_SPECIFIC_FOLDER_ID_STR);
+       gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+       command_page.command_cont_folder_specific = button;
+       gtk_widget_show(button);
+       gtk_widget_show(hbox);
+
+       notify_command_enable_set_sensitivity
+       (GTK_TOGGLE_BUTTON(command_page.command_enabled), NULL);
+       notify_command_folder_specific_set_sensitivity
+       (GTK_TOGGLE_BUTTON(command_page.command_folder_specific), NULL);
+       gtk_widget_show(pvbox);
+       command_page.page.widget = pvbox;
+}
+
+static void notify_destroy_command_page(PrefsPage *page)
+{
+}
+
+static void notify_save_command(PrefsPage *page)
+{
+       gdouble timeout;
+       const gchar *tmp_str;
+
+       notify_config.command_enabled =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(command_page.command_enabled));
+
+       timeout =
+       gtk_spin_button_get_value(GTK_SPIN_BUTTON(command_page.command_timeout));
+       notify_config.command_timeout = (gint)floor(timeout*1000+0.5);
+       notify_config.command_folder_specific =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(command_page.command_folder_specific));
+
+       tmp_str = gtk_entry_get_text(GTK_ENTRY(command_page.command_line));
+       if(notify_config.command_line)
+      g_free(notify_config.command_line);
+       notify_config.command_line = g_strdup(tmp_str);
+}
+
+static void notify_command_enable_set_sensitivity(GtkToggleButton *button,
+               gpointer data)
+{
+       gboolean active;
+       active = gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(command_page.command_enabled));
+       gtk_widget_set_sensitive(command_page.command_cont_enable, active);
+}
+
+static void notify_command_folder_specific_set_sensitivity(GtkToggleButton *bu,
+               gpointer data)
+{
+       gboolean active;
+       active = gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(command_page.command_folder_specific));
+       gtk_widget_set_sensitive(command_page.command_cont_folder_specific, active);
+}
+
+#endif /* NOTIFICATION_COMMAND */
+
+#ifdef NOTIFICATION_LCDPROC
+static void notify_create_lcdproc_page(PrefsPage *page, GtkWindow *window,
+               gpointer data)
+{
+       GtkWidget *pvbox;
+       GtkWidget *vbox;
+       GtkWidget *label;
+       GtkWidget *entry;
+       GtkWidget *spinner;
+       GtkWidget *checkbox;
+       GtkWidget *hbox;
+
+       pvbox = gtk_vbox_new(FALSE, 20);
+       gtk_container_set_border_width(GTK_CONTAINER(pvbox), 10);
+
+       /* Enable lcdproc */
+       checkbox = gtk_check_button_new_with_label(_("Enable LCD"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.lcdproc_enabled);
+       gtk_box_pack_start(GTK_BOX(pvbox), checkbox, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(checkbox), "toggled",
+                       G_CALLBACK(notify_lcdproc_enable_set_sensitivity), NULL);
+       gtk_widget_show(checkbox);
+       lcdproc_page.lcdproc_enabled = checkbox;
+
+       /* Container vbox for greying out everything */
+       vbox = gtk_vbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(pvbox), vbox, FALSE, FALSE, 0);
+       gtk_widget_show(vbox);
+       lcdproc_page.lcdproc_cont_enable = vbox;
+
+       /* Hostname and port information */
+       hbox = gtk_hbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       gtk_widget_show(hbox);
+       label = gtk_label_new(_("Hostname:Port of LCDd server:"));
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       entry = gtk_entry_new();
+       gtk_entry_set_text(GTK_ENTRY(entry), notify_config.lcdproc_hostname);
+       gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 0);
+       gtk_widget_show(entry);
+       gtk_widget_show(label);
+       label = gtk_label_new(":");
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       gtk_widget_show(label);
+       spinner = gtk_spin_button_new_with_range(1., 65535., 1.);
+       gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinner), 0);
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinner),
+                       notify_config.lcdproc_port);
+       gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
+       gtk_widget_show(spinner);
+       lcdproc_page.lcdproc_hostname = entry;
+       lcdproc_page.lcdproc_port = spinner;
+
+       notify_lcdproc_enable_set_sensitivity
+       (GTK_TOGGLE_BUTTON(lcdproc_page.lcdproc_enabled), NULL);
+       gtk_widget_show(pvbox);
+       lcdproc_page.page.widget = pvbox;
+}
+
+static void notify_destroy_lcdproc_page(PrefsPage *page)
+{
+}
+
+static void notify_save_lcdproc(PrefsPage *page)
+{
+       notify_config.lcdproc_enabled =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(lcdproc_page.lcdproc_enabled));
+
+       if(notify_config.lcdproc_hostname)
+       g_free(notify_config.lcdproc_hostname);
+       notify_config.lcdproc_hostname =
+       g_strdup(gtk_entry_get_text(GTK_ENTRY(lcdproc_page.lcdproc_hostname)));
+
+       notify_config.lcdproc_port =
+       gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(lcdproc_page.lcdproc_port));
+
+       if(notify_config.lcdproc_enabled)
+       notification_lcdproc_connect();
+       else
+       notification_lcdproc_disconnect();
+}
+
+static void notify_lcdproc_enable_set_sensitivity(GtkToggleButton *button,
+               gpointer data)
+{
+       gboolean active;
+       active = gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(lcdproc_page.lcdproc_enabled));
+       gtk_widget_set_sensitive(lcdproc_page.lcdproc_cont_enable, active);
+}
+#endif /* NOTIFICATION_LCDPROC */
+
+#ifdef NOTIFICATION_TRAYICON
+static void notify_create_trayicon_page(PrefsPage *page, GtkWindow *window,
+               gpointer data)
+{
+       GtkWidget *pvbox;
+       GtkWidget *vbox;
+       GtkWidget *checkbox;
+       GtkWidget *hbox;
+       GtkWidget *button;
+
+#if defined(HAVE_LIBNOTIFY)
+       GtkWidget *frame;
+       GtkWidget *svbox;
+       GtkWidget *label;
+#endif
+
+#ifdef HAVE_LIBNOTIFY
+       GtkWidget *ssvbox;
+       GtkWidget *spinner;
+       gdouble timeout;
+#endif
+
+       pvbox = gtk_vbox_new(FALSE, 20);
+       gtk_container_set_border_width(GTK_CONTAINER(pvbox), 10);
+
+       /* Enable trayicon */
+       checkbox = gtk_check_button_new_with_label(_("Enable Trayicon"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.trayicon_enabled);
+       gtk_box_pack_start(GTK_BOX(pvbox), checkbox, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(checkbox), "toggled",
+                       G_CALLBACK(notify_trayicon_enable_set_sensitivity), NULL);
+       gtk_widget_show(checkbox);
+       trayicon_page.trayicon_enabled = checkbox;
+
+       /* Container vbox for greying out everything */
+       vbox = gtk_vbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(pvbox), vbox, FALSE, FALSE, 0);
+       gtk_widget_show(vbox);
+       trayicon_page.trayicon_cont_enable = vbox;
+
+       /* Hide at startup */
+       checkbox = gtk_check_button_new_with_label(_("Hide at start-up"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.trayicon_hide_at_startup);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       trayicon_page.trayicon_hide_at_startup = checkbox;
+
+       /* Close to tray */
+       checkbox = gtk_check_button_new_with_label(_("Close to tray"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.trayicon_close_to_tray);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       trayicon_page.trayicon_close_to_tray = checkbox;
+
+       /* Hide when iconified */
+       checkbox = gtk_check_button_new_with_label(_("Hide when iconified"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.trayicon_hide_when_iconified);
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       trayicon_page.trayicon_hide_when_iconified = checkbox;
+
+       /* folder specific */
+       hbox = gtk_hbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+       gtk_widget_show(hbox);
+       checkbox = gtk_check_button_new_with_label(_("Only include selected folders"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.trayicon_folder_specific);
+       gtk_box_pack_start(GTK_BOX(hbox), checkbox, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(checkbox), "toggled",
+                       G_CALLBACK(notify_trayicon_folder_specific_set_sensitivity),
+                       NULL);
+       gtk_widget_show(checkbox);
+       trayicon_page.trayicon_folder_specific = checkbox;
+       button = gtk_button_new_with_label(_("Select folders..."));
+       g_signal_connect(G_OBJECT(button), "clicked",
+                       G_CALLBACK(notification_foldercheck_sel_folders_cb),
+                       TRAYICON_SPECIFIC_FOLDER_ID_STR);
+       gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+       trayicon_page.trayicon_cont_folder_specific = button;
+       gtk_widget_show(button);
+
+#ifdef HAVE_LIBNOTIFY
+       /* Frame for trayicon popup stuff */
+
+       /* TRANSLATORS: "Toaster" does not refer to the kitchen appliance.
+                A toast is a small passive (non-focus-stealing)
+                notification bubble. If your language does not have a word
+                for that, go for something along the lines of "passive popup"
+                instead.See also
+                http://en.wikipedia.org/wiki/Toast_(computing) */
+       frame = gtk_frame_new(_("Passive toaster popup"));
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
+       gtk_widget_show(frame);
+
+       /* vbox for frame */
+       svbox = gtk_vbox_new(FALSE, 10);
+       gtk_container_set_border_width(GTK_CONTAINER(svbox), 5);
+       gtk_container_add(GTK_CONTAINER(frame), svbox);
+       gtk_widget_show(svbox);
+
+       /* Enable popup for the tray icon */
+       checkbox = gtk_check_button_new_with_label(_("Enable Popup"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.trayicon_popup_enabled);
+       g_signal_connect(G_OBJECT(checkbox), "toggled",
+                       G_CALLBACK(notify_trayicon_popup_enable_set_sensitivity),
+                       NULL);
+       gtk_box_pack_start(GTK_BOX(svbox), checkbox, FALSE, FALSE, 0);
+       gtk_widget_show(checkbox);
+       trayicon_page.trayicon_popup_enabled = checkbox;
+
+       /* vbox for trayicon popup stuff enabled/disabled container */
+       ssvbox = gtk_vbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(svbox), ssvbox, FALSE, FALSE, 0);
+       gtk_widget_show(ssvbox);
+       trayicon_page.trayicon_popup_cont_enable = ssvbox;
+
+       /* timeout */
+       hbox = gtk_hbox_new(FALSE, 10);
+       label = gtk_label_new(_("Popup timeout:"));
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       gtk_widget_show(label);
+       spinner = gtk_spin_button_new_with_range(TIMEOUT_SPINNER_MIN, TIMEOUT_SPINNER_MAX, TIMEOUT_SPINNER_STEP);
+       gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinner), 1);
+       timeout = notify_config.trayicon_popup_timeout/1000.;
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinner), timeout);
+       gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(ssvbox), hbox, FALSE, FALSE, 0);
+       label = gtk_label_new(_("seconds"));
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+
+       checkbox = gtk_check_button_new_with_label(_("Display folder name"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.trayicon_display_folder_name);
+       gtk_box_pack_start(GTK_BOX(ssvbox), checkbox, FALSE, FALSE, 0);
+       trayicon_page.trayicon_display_folder_name = checkbox;
+       gtk_widget_show(checkbox);
+
+       gtk_widget_show(spinner);
+       gtk_widget_show(label);
+       gtk_widget_show(hbox);
+       trayicon_page.trayicon_popup_timeout = spinner;
+
+#endif
+
+       notify_trayicon_enable_set_sensitivity
+       (GTK_TOGGLE_BUTTON(trayicon_page.trayicon_enabled), NULL);
+
+#ifdef HAVE_LIBNOTIFY
+       notify_trayicon_popup_enable_set_sensitivity
+       (GTK_TOGGLE_BUTTON(trayicon_page.trayicon_popup_enabled), NULL);
+#endif
+
+       notify_trayicon_folder_specific_set_sensitivity
+       (GTK_TOGGLE_BUTTON(trayicon_page.trayicon_folder_specific), NULL);
+
+       gtk_widget_show(pvbox);
+       trayicon_page.page.widget = pvbox;
+}
+
+static void notify_destroy_trayicon_page(PrefsPage *page)
+{
+}
+
+static void notify_save_trayicon(PrefsPage *page)
+{
+#ifdef HAVE_LIBNOTIFY
+       gdouble timeout;
+#endif
+
+       notify_config.trayicon_enabled =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(trayicon_page.trayicon_enabled));
+
+       notify_config.trayicon_hide_at_startup =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(trayicon_page.trayicon_hide_at_startup));
+
+       notify_config.trayicon_close_to_tray =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(trayicon_page.trayicon_close_to_tray));
+
+       notify_config.trayicon_hide_when_iconified =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(trayicon_page.trayicon_hide_when_iconified));
+
+#ifdef HAVE_LIBNOTIFY
+       notify_config.trayicon_popup_enabled =
+       gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(trayicon_page.trayicon_popup_enabled));
+
+       timeout =
+       gtk_spin_button_get_value(GTK_SPIN_BUTTON
+                       (trayicon_page.trayicon_popup_timeout));
+       notify_config.trayicon_popup_timeout = (gint)floor(timeout*1000+0.5);
+
+       notify_config.trayicon_display_folder_name =
+               gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(trayicon_page.trayicon_display_folder_name));
+#endif
+
+       notify_config.trayicon_folder_specific =
+               gtk_toggle_button_get_active
+               (GTK_TOGGLE_BUTTON(trayicon_page.trayicon_folder_specific));
+
+       if(notify_config.trayicon_enabled)
+       notification_update_msg_counts(NULL);
+       else
+       notification_trayicon_destroy();
+}
+
+static void notify_trayicon_enable_set_sensitivity(GtkToggleButton *button,
+               gpointer data)
+{
+       gboolean active;
+       active = gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(trayicon_page.trayicon_enabled));
+       gtk_widget_set_sensitive(trayicon_page.trayicon_cont_enable, active);
+}
+
+static void notify_trayicon_folder_specific_set_sensitivity(GtkToggleButton *bu,
+               gpointer data)
+{
+       gboolean active;
+       active = gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(trayicon_page.trayicon_folder_specific));
+       gtk_widget_set_sensitive(trayicon_page.trayicon_cont_folder_specific, active);
+}
+
+#ifdef HAVE_LIBNOTIFY
+static void notify_trayicon_popup_enable_set_sensitivity(GtkToggleButton *bu,
+               gpointer data)
+{
+       gboolean active;
+       active = gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(trayicon_page.trayicon_popup_enabled));
+       gtk_widget_set_sensitive(trayicon_page.trayicon_popup_cont_enable, active);
+}
+
+#endif /* HAVE_LIBNOTIFY */
+
+#endif /* NOTIFICATION_TRAYICON */
+
+#ifdef NOTIFICATION_INDICATOR
+
+#define NOTIFICATION_INDICATOR_REGISTER_PATH ".config/indicators/messages/applications"
+
+static gboolean indicator_register_file_exists(void)
+{
+  gboolean retval;
+  gchar *filepath;
+  filepath = g_strconcat(get_home_dir(), "/" NOTIFICATION_INDICATOR_REGISTER_PATH "/claws-mail", NULL);
+  retval = is_file_exist(filepath);
+  g_free(filepath);
+  return retval;
+}
+
+static gboolean indicator_register_file_create(void)
+{
+  FILE *fp;
+  gchar *path, *filepath;
+  gboolean success;
+
+  /* make sure directory path exists */
+  path = g_strconcat(get_home_dir(), "/" NOTIFICATION_INDICATOR_REGISTER_PATH, NULL);
+  if(!is_dir_exist(path)) {
+    if(make_dir_hier(path) != -1)
+      debug_print("Notification plugin: Created directory '%s'\n", path);
+     else {
+       debug_print("Notification plugin: Error creating directory '%s'\n", path);
+       g_free(path);
+       return FALSE;
+     }
+  }
+
+  /* create register file */
+  filepath = g_strconcat(path, "/claws-mail", NULL);
+  g_free(path);
+  fp = fopen(filepath, "w");
+  success = (fp != NULL);
+  if(fp) {
+    fprintf(fp, "%s\n", get_desktop_file());
+    fclose(fp);
+    debug_print("Notification plugin: Created desktop indicator file '%s'\n", filepath);
+  }
+  else
+    debug_print("Notification plugin: Could not create desktop indicator file '%s'\n", filepath);
+  g_free(filepath);
+
+  return success;
+}
+
+static gboolean indicator_register_file_remove(void)
+{
+  gchar *filepath;
+  int retval;
+
+  filepath = g_strconcat(get_home_dir(), "/" NOTIFICATION_INDICATOR_REGISTER_PATH "/claws-mail", NULL);
+  retval = claws_unlink(filepath);
+  if(retval != -1)
+    debug_print("Notification plugin: Deleted file '%s'\n", filepath);
+  else
+    debug_print("Notification plugin: Error deleting file '%s'\n", filepath);
+  g_free(filepath);
+
+  return (retval != -1);
+}
+
+static void notify_create_indicator_page(PrefsPage *page, GtkWindow *window,
+               gpointer data)
+{
+       GtkWidget *pvbox;
+       GtkWidget *vbox;
+       GtkWidget *checkbox;
+       GtkWidget *ind_reg;
+
+       pvbox = gtk_vbox_new(FALSE, 20);
+       gtk_container_set_border_width(GTK_CONTAINER(pvbox), 10);
+
+       /* Enable indicator */
+       checkbox = gtk_check_button_new_with_label(_("Add to Indicator Applet"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                       notify_config.indicator_enabled);
+       gtk_box_pack_start(GTK_BOX(pvbox), checkbox, FALSE, FALSE, 0);
+       g_signal_connect(G_OBJECT(checkbox), "toggled",
+                       G_CALLBACK(notify_indicator_enable_set_sensitivity), NULL);
+       indicator_page.indicator_enabled = checkbox;
+
+       /* Container vbox for greying out everything */
+       vbox = gtk_vbox_new(FALSE, 10);
+       gtk_box_pack_start(GTK_BOX(pvbox), vbox, FALSE, FALSE, 0);
+       indicator_page.indicator_cont_enable = vbox;
+
+       /* hide when minimized */
+       checkbox = gtk_check_button_new_with_label(_("Hide mainwindow when minimized"));
+       gtk_box_pack_start(GTK_BOX(indicator_page.indicator_cont_enable), checkbox, FALSE, FALSE, 0);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox), notify_config.indicator_hide_minimized);
+       indicator_page.indicator_hide_minimized = checkbox;
+
+       /* register */
+       ind_reg = gtk_check_button_new_with_label(_("Register Claws Mail"));
+       gtk_box_pack_start(GTK_BOX(indicator_page.indicator_cont_enable), ind_reg, FALSE, FALSE, 0);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ind_reg), indicator_register_file_exists());
+       indicator_page.indicator_register = ind_reg;
+
+       notify_indicator_enable_set_sensitivity(GTK_TOGGLE_BUTTON(indicator_page.indicator_enabled), NULL);
+       gtk_widget_show_all(pvbox);
+       indicator_page.page.widget = pvbox;
+}
+
+static void notify_destroy_indicator_page(PrefsPage *page)
+{
+}
+
+static void notify_save_indicator(PrefsPage *page)
+{
+    gboolean ind_reg;
+
+       notify_config.indicator_enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(indicator_page.indicator_enabled));
+
+       notify_config.indicator_hide_minimized = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(indicator_page.indicator_hide_minimized));
+
+       ind_reg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(indicator_page.indicator_register));
+       if(ind_reg) {
+         if(!indicator_register_file_exists())
+           indicator_register_file_create();
+       }
+       else {
+         if(indicator_register_file_exists())
+           indicator_register_file_remove();
+       }
+
+       notification_indicator_destroy();
+       notification_update_indicator();
+}
+
+static void notify_indicator_enable_set_sensitivity(GtkToggleButton *button,
+               gpointer data)
+{
+       gboolean active;
+       active = gtk_toggle_button_get_active
+       (GTK_TOGGLE_BUTTON(indicator_page.indicator_enabled));
+       gtk_widget_set_sensitive(indicator_page.indicator_cont_enable, active);
+}
+#endif /* NOTIFICATION_INDICATOR */
+
+#ifdef NOTIFICATION_HOTKEYS
+static void notify_create_hotkeys_page(PrefsPage *page, GtkWindow *window, gpointer data)
+{
+    GtkWidget *pvbox;
+    GtkWidget *vbox;
+    GtkWidget *checkbox;
+    GtkWidget *label;
+    gchar *markup;
+    GtkWidget *table;
+    GtkWidget *entry;
+
+    pvbox = gtk_vbox_new(FALSE, 20);
+    gtk_container_set_border_width(GTK_CONTAINER(pvbox), 10);
+
+    /* Enable hotkeys */
+    checkbox = gtk_check_button_new_with_label(_("Enable global hotkeys"));
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),  notify_config.hotkeys_enabled);
+    gtk_box_pack_start(GTK_BOX(pvbox), checkbox, FALSE, FALSE, 0);
+    g_signal_connect(G_OBJECT(checkbox), "toggled",
+        G_CALLBACK(notify_hotkeys_enable_set_sensitivity), NULL);
+    hotkeys_page.hotkeys_enabled = checkbox;
+
+    /* Container vbox for greying out everything */
+    vbox = gtk_vbox_new(FALSE, 10);
+    gtk_box_pack_start(GTK_BOX(pvbox), vbox, FALSE, FALSE, 0);
+    hotkeys_page.hotkeys_cont_enable = vbox;
+
+    /* description */
+    label = gtk_label_new("");
+    markup = g_markup_printf_escaped(_("Examples for hotkeys include <b>%s</b> and <b>%s</b>"), _("<control><shift>F11"), _("<alt>N"));
+    gtk_label_set_markup(GTK_LABEL(label), markup);
+    gtk_misc_set_alignment(GTK_MISC(label), 0., 0.);
+    g_free(markup);
+    gtk_box_pack_start(GTK_BOX(hotkeys_page.hotkeys_cont_enable), label, FALSE, FALSE, 0);
+
+    /* table for entry fields */
+    table = gtk_table_new(1, 2, FALSE);
+    gtk_box_pack_start(GTK_BOX(hotkeys_page.hotkeys_cont_enable), table, FALSE, FALSE, 0);
+
+    /* toggle mainwindow */
+    label = gtk_label_new(_("Toggle minimize:"));
+    gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
+    entry = gtk_entry_new();
+    gtk_table_attach_defaults(GTK_TABLE(table), entry, 1, 2, 0, 1);
+    if(notify_config.hotkeys_toggle_mainwindow)
+      gtk_entry_set_text(GTK_ENTRY(entry), notify_config.hotkeys_toggle_mainwindow);
+    hotkeys_page.hotkeys_toggle_mainwindow = entry;
+
+    notify_hotkeys_enable_set_sensitivity(GTK_TOGGLE_BUTTON(hotkeys_page.hotkeys_enabled), NULL);
+    gtk_widget_show_all(pvbox);
+    hotkeys_page.page.widget = pvbox;
+}
+
+static void notify_destroy_hotkeys_page(PrefsPage *page)
+{
+}
+
+static void notify_save_hotkeys(PrefsPage *page)
+{
+    const gchar *tmp_str;
+    notify_config.hotkeys_enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(hotkeys_page.hotkeys_enabled));
+
+    tmp_str = gtk_entry_get_text(GTK_ENTRY(hotkeys_page.hotkeys_toggle_mainwindow));
+    if(notify_config.hotkeys_toggle_mainwindow)
+      g_free(notify_config.hotkeys_toggle_mainwindow);
+    notify_config.hotkeys_toggle_mainwindow = g_strdup(tmp_str);
+
+    notification_hotkeys_update_bindings();
+}
+
+static void notify_hotkeys_enable_set_sensitivity(GtkToggleButton *button,
+        gpointer data)
+{
+    gboolean active;
+    active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(hotkeys_page.hotkeys_enabled));
+    gtk_widget_set_sensitive(hotkeys_page.hotkeys_cont_enable, active);
+}
+#endif /* hotkeys */
+
+
+/* This feels so wrong... */
+static gint conv_color_to_int(GdkColor *color)
+{
+       gint result;
+       guint red, green, blue;
+
+       red = (guint) ((gdouble)(color->red) /65535.*255.);
+       green = (guint) ((gdouble)(color->green) /65535.*255.);
+       blue = (guint) ((gdouble)(color->blue) /65535.*255.);
+
+       result = (gint) (blue | (green<<8)| (red<<16));
+
+       return result;
+}
diff --git a/src/plugins/notification/notification_prefs.h b/src/plugins/notification/notification_prefs.h
new file mode 100644 (file)
index 0000000..684f6ca
--- /dev/null
@@ -0,0 +1,118 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NOTIFICATION_PREFS_H
+#define NOTIFICATION_PREFS_H NOTIFICATION_PREFS_H
+
+#include "pluginconfig.h"
+
+#include <glib.h>
+
+#include "prefs_gtk.h"
+
+#include "notification_banner.h"
+
+#ifdef NOTIFICATION_BANNER
+typedef enum {
+  NOTIFY_BANNER_SHOW_NEVER = 0,
+  NOTIFY_BANNER_SHOW_ALWAYS,
+  NOTIFY_BANNER_SHOW_NONEMPTY
+} NotifyBannerShow;
+#endif
+
+typedef struct {
+  gboolean         include_mail;
+  gboolean         include_news;
+  gboolean         include_rss;
+  gboolean         include_calendar;
+  gboolean         urgency_hint_new;
+  gboolean         urgency_hint_unread;
+#ifdef HAVE_LIBCANBERRA_GTK
+  gboolean         canberra_play_sounds;
+#endif
+#ifdef NOTIFICATION_BANNER
+  NotifyBannerShow banner_show;
+  gint             banner_speed;
+  gboolean         banner_include_unread;
+  gint             banner_max_msgs;
+  gboolean         banner_sticky;
+  gint             banner_root_x;
+  gint             banner_root_y;
+  gboolean         banner_folder_specific;
+  gboolean         banner_enable_colors;
+  gulong           banner_color_bg;
+  gulong           banner_color_fg;
+       gint             banner_width;
+#endif
+#ifdef NOTIFICATION_POPUP
+  gboolean         popup_show;
+  gint             popup_timeout;
+  gboolean         popup_folder_specific;
+#ifndef HAVE_LIBNOTIFY
+  gboolean         popup_sticky;
+  gint             popup_root_x;
+  gint             popup_root_y;
+  gint             popup_width;
+  gboolean         popup_enable_colors;
+  gulong           popup_color_bg;
+  gulong           popup_color_fg;
+#else /* HAVE_LIBNOTIFY */
+  gboolean         popup_display_folder_name;
+#endif /* HAVE_LIBNOTIFY */
+#endif /* Popup */
+#ifdef NOTIFICATION_COMMAND
+  gboolean         command_enabled;
+  gint             command_timeout;
+  gboolean         command_folder_specific;
+  gchar*           command_line;
+#endif
+#ifdef NOTIFICATION_LCDPROC
+  gboolean         lcdproc_enabled;
+  gchar*           lcdproc_hostname;
+  guint           lcdproc_port;
+
+#endif
+#ifdef NOTIFICATION_TRAYICON
+  gboolean         trayicon_enabled;
+  gboolean         trayicon_hide_at_startup;
+  gboolean         trayicon_close_to_tray;
+  gboolean         trayicon_hide_when_iconified;
+  gboolean         trayicon_folder_specific;
+#ifdef HAVE_LIBNOTIFY
+  gboolean         trayicon_display_folder_name;
+  gboolean         trayicon_popup_enabled;
+  gint             trayicon_popup_timeout;
+#endif /* HAVE_LIBNOTIFY */
+#endif /* Trayicon */
+#ifdef NOTIFICATION_INDICATOR
+  gboolean indicator_enabled;
+  gboolean indicator_hide_minimized;
+#endif /* NOTIFICATION_INDICATOR */
+#ifdef NOTIFICATION_HOTKEYS
+  gboolean hotkeys_enabled;
+  gchar* hotkeys_toggle_mainwindow;
+#endif /* Hotkeys */
+} NotifyPrefs;
+
+extern NotifyPrefs notify_config;
+extern PrefParam   notify_param[];
+
+void notify_gtk_init(void);
+void notify_gtk_done(void);
+void notify_save_config(void);
+
+#endif /* NOTIFICATION_PREFS_H */
diff --git a/src/plugins/notification/notification_trayicon.c b/src/plugins/notification/notification_trayicon.c
new file mode 100644 (file)
index 0000000..4aa5e11
--- /dev/null
@@ -0,0 +1,948 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt and the Claws Mail Team.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This module is of course inspired by the trayicon plugin which is
+ * shipped with Claws-Mail, copyrighted by the Claws-Mail Team. */
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_TRAYICON
+
+#include "notification_trayicon.h"
+#include "notification_prefs.h"
+#include "notification_core.h"
+#include "notification_hotkeys.h"
+#include "notification_pixbuf.h"
+#include "notification_foldercheck.h"
+
+#include "main.h"
+#include "account.h"
+#include "mainwindow.h"
+#include "prefs_common.h"
+#include "alertpanel.h"
+#include "gtk/menu.h"
+#ifndef USE_NEW_ADDRBOOK
+    #include "addressbook.h"
+    #include "addrindex.h"
+#else
+    #include "addressbook-dbus.h"
+#endif
+
+#include "gtk/manage_window.h"
+#include "common/utils.h"
+#include "gtk/gtkutils.h"
+
+static GdkPixbuf* notification_trayicon_create(void);
+
+static void notification_trayicon_on_popup_menu(GtkStatusIcon*,guint,
+                                               guint,gpointer);
+static gboolean notification_trayicon_on_size_changed(GtkStatusIcon*,
+                                                     gint, gpointer);
+
+static void trayicon_get_all_cb(GtkAction*, gpointer);
+static void trayicon_compose_cb(GtkAction*, gpointer);
+static void trayicon_compose_acc_cb(GtkMenuItem*, gpointer);
+static void trayicon_addressbook_cb(GtkAction*, gpointer);
+static void trayicon_exit_cb(GtkAction*,gpointer);
+static void trayicon_toggle_offline_cb(GtkAction*,gpointer);
+#ifdef HAVE_LIBNOTIFY
+static void trayicon_toggle_notify_cb(GtkAction*,gpointer);
+#endif
+
+#ifdef HAVE_LIBNOTIFY
+#include <libnotify/notify.h>
+
+#ifndef NOTIFY_CHECK_VERSION
+# define NOTIFY_CHECK_VERSION(a,b,c) 0
+#endif
+
+typedef struct {
+  gint count;
+  gint num_mail;
+  gint num_news;
+  gint num_calendar;
+  gint num_rss;
+  gchar *msg_path;
+  NotifyNotification *notification;
+  GError *error;
+} NotificationTrayiconPopup;
+
+static NotificationTrayiconPopup popup;
+
+static gboolean notification_trayicon_popup_add_msg(MsgInfo*,
+                                                   NotificationFolderType);
+static gboolean notification_trayicon_popup_create(MsgInfo*,
+                                                  NotificationFolderType);
+static void popup_timeout_fun(NotifyNotification*, gpointer);
+static void notification_trayicon_popup_free_func(gpointer);
+static void notification_trayicon_popup_default_action_cb(NotifyNotification*,
+                                                         const char*,void*);
+static gchar* notification_trayicon_popup_assemble_summary(void);
+static gchar* notification_trayicon_popup_assemble_body(MsgInfo*);
+static void   notification_trayicon_popup_count_msgs(NotificationFolderType);
+
+G_LOCK_DEFINE_STATIC(trayicon_popup);
+
+#endif
+
+static GtkStatusIcon *trayicon;
+static gboolean updating_menu = FALSE;
+static GtkWidget *traymenu_popup;
+
+static GtkActionEntry trayicon_popup_menu_entries[] = {
+       {"SysTrayiconPopup", NULL, "SysTrayiconPopup" },
+       {"SysTrayiconPopup/GetMail", NULL, N_("_Get Mail"), NULL, NULL, G_CALLBACK(trayicon_get_all_cb) },
+       {"SysTrayiconPopup/---", NULL, "---" },
+       {"SysTrayiconPopup/Email", NULL, N_("_Email"), NULL, NULL, G_CALLBACK(trayicon_compose_cb) },
+       {"SysTrayiconPopup/EmailAcc", NULL, N_("E_mail from account"), NULL, NULL, NULL },
+       {"SysTrayiconPopup/OpenAB", NULL, N_("Open A_ddressbook"), NULL, NULL, G_CALLBACK(trayicon_addressbook_cb) },
+       {"SysTrayiconPopup/Exit", NULL, N_("E_xit Claws Mail"), NULL, NULL, G_CALLBACK(trayicon_exit_cb) },
+};
+
+static GtkToggleActionEntry trayicon_popup_toggle_menu_entries[] =
+{
+       {"SysTrayiconPopup/ToggleOffline",              NULL, N_("_Work Offline"), NULL, NULL, G_CALLBACK(trayicon_toggle_offline_cb) },
+#ifdef HAVE_LIBNOTIFY
+       {"SysTrayiconPopup/ShowBubbles", NULL, N_("Show Trayicon Notifications"), NULL, NULL, G_CALLBACK(trayicon_toggle_notify_cb) },
+#endif
+};
+
+
+void notification_trayicon_msg(MsgInfo *msginfo)
+{
+#ifndef HAVE_LIBNOTIFY
+  return;
+
+#else
+  FolderType ftype;
+  NotificationFolderType nftype;
+  gchar *uistr;
+
+  nftype = F_TYPE_MAIL;
+
+  if(!msginfo || !notify_config.trayicon_enabled ||
+     !notify_config.trayicon_popup_enabled ||
+     !MSG_IS_NEW(msginfo->flags))
+    return;
+
+  if(notify_config.trayicon_folder_specific) {
+    guint id;
+    GSList *list;
+    gchar *identifier;
+    gboolean found = FALSE;
+
+    if(!(msginfo->folder))
+      return;
+
+    identifier = folder_item_get_identifier(msginfo->folder);
+
+    id =
+      notification_register_folder_specific_list
+      (TRAYICON_SPECIFIC_FOLDER_ID_STR);
+    list = notification_foldercheck_get_list(id);
+    for(; (list != NULL) && !found; list = g_slist_next(list)) {
+      gchar *list_identifier;
+      FolderItem *list_item = (FolderItem*) list->data;
+
+      list_identifier = folder_item_get_identifier(list_item);
+      if(!strcmp2(list_identifier, identifier))
+       found = TRUE;
+
+      g_free(list_identifier);
+    }
+    g_free(identifier);
+
+    if(!found)
+      return;
+  } /* folder specific */
+
+  ftype = msginfo->folder->folder->klass->type;
+
+  G_LOCK(trayicon_popup);
+  /* Check out which type to notify about */
+  switch(ftype) {
+  case F_MH:
+  case F_MBOX:
+  case F_MAILDIR:
+  case F_IMAP:
+    nftype = F_TYPE_MAIL;
+    break;
+  case F_NEWS:
+    nftype = F_TYPE_NEWS;
+    break;
+  case F_UNKNOWN:
+    if((uistr = msginfo->folder->folder->klass->uistr) == NULL) {
+      G_UNLOCK(trayicon_popup);
+      return;
+    }
+    else if(!strcmp(uistr, "vCalendar"))
+      nftype = F_TYPE_CALENDAR;
+    else if(!strcmp(uistr, "RSSyl"))
+      nftype = F_TYPE_RSS;
+    else {
+      debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
+      G_UNLOCK(trayicon_popup);
+      return;
+    }
+    break;
+  default:
+    debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
+    G_UNLOCK(trayicon_popup);
+    return;
+  }
+
+
+  notification_trayicon_popup_add_msg(msginfo, nftype);
+
+  G_UNLOCK(trayicon_popup);
+
+#endif /* HAVE_LIBNOTIFY */
+}
+
+void notification_trayicon_destroy(void)
+{
+  if(trayicon) {
+    gtk_status_icon_set_visible(trayicon, FALSE);
+    g_object_unref(trayicon);
+    trayicon = NULL;
+  }
+}
+
+void notification_update_trayicon()
+{
+  gchar *buf;
+  static GdkPixbuf *old_icon = NULL;
+  GdkPixbuf *new_icon;
+  gint offset;
+  NotificationMsgCount count;
+  GSList *list;
+
+  if(!notify_config.trayicon_enabled)
+    return;
+
+  if(notify_config.trayicon_folder_specific) {
+    guint id;
+    id =
+      notification_register_folder_specific_list
+      (TRAYICON_SPECIFIC_FOLDER_ID_STR);
+    list = notification_foldercheck_get_list(id);
+  }
+  else
+    list = NULL;
+
+  notification_core_get_msg_count(list, &count);
+
+  if(!trayicon) {
+
+#ifdef NOTIFICATION_HOTKEYS
+    notification_hotkeys_update_bindings();
+#endif
+
+    old_icon = notification_trayicon_create();
+    if(!trayicon) {
+      debug_print("Notification plugin: Could not create trayicon\n");
+      return;
+    }
+  }
+
+  /* Tooltip */
+  buf = g_strdup_printf(_("New %d, Unread: %d, Total: %d"),
+                       count.new_msgs, count.unread_msgs,
+                       count.total_msgs);
+#if GTK_CHECK_VERSION(2,16,0)
+  gtk_status_icon_set_tooltip_text(trayicon, buf);
+#else
+  gtk_status_icon_set_tooltip(trayicon, buf);
+#endif
+  g_free(buf);
+
+  /* Pixmap */
+  (prefs_common_get_prefs()->work_offline) ? (offset = 1) : (offset = 0);
+
+  if((count.new_msgs > 0) && (count.unreadmarked_msgs > 0))
+    new_icon =
+      notification_pixbuf_get(NOTIFICATION_TRAYICON_NEWMARKEDMAIL+offset);
+  else if(count.new_msgs > 0)
+    new_icon =
+      notification_pixbuf_get(NOTIFICATION_TRAYICON_NEWMAIL+offset);
+  else if(count.unreadmarked_msgs > 0)
+    new_icon =
+      notification_pixbuf_get(NOTIFICATION_TRAYICON_UNREADMARKEDMAIL+offset);
+  else if(count.unread_msgs > 0)
+    new_icon =
+      notification_pixbuf_get(NOTIFICATION_TRAYICON_UNREADMAIL+offset);
+  else
+    new_icon =
+      notification_pixbuf_get(NOTIFICATION_TRAYICON_NOMAIL+offset);
+
+  if(new_icon != old_icon) {
+    gtk_status_icon_set_from_pixbuf(trayicon, new_icon);
+    old_icon = new_icon;
+  }
+}
+
+gboolean notification_trayicon_main_window_close(gpointer source, gpointer data)
+{
+  if(!notify_config.trayicon_enabled)
+    return FALSE;
+
+  if(source) {
+    gboolean *close_allowed = (gboolean*)source;
+
+    if(notify_config.trayicon_close_to_tray) {
+      MainWindow *mainwin = mainwindow_get_mainwindow();
+
+      *close_allowed = FALSE;
+      if(mainwin && gtk_widget_get_visible(GTK_WIDGET(mainwin->window)))
+       main_window_hide(mainwin);
+    }
+  }
+  return FALSE;
+}
+
+gboolean notification_trayicon_main_window_got_iconified(gpointer source,
+                                                        gpointer data)
+{
+  MainWindow *mainwin = mainwindow_get_mainwindow();
+
+  if(!notify_config.trayicon_enabled)
+    return FALSE;
+
+  if(notify_config.trayicon_hide_when_iconified &&
+     mainwin && gtk_widget_get_visible(GTK_WIDGET(mainwin->window))
+     && !gtk_window_get_skip_taskbar_hint(GTK_WINDOW(mainwin->window))) {
+    gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin->window), TRUE);
+  }
+  return FALSE;
+}
+
+gboolean notification_trayicon_account_list_changed(gpointer source,
+                                                   gpointer data)
+{
+  GList *cur_ac;
+       GtkWidget *menu, *submenu;
+  GtkWidget *menuitem;
+  PrefsAccount *ac_prefs;
+
+  GList *account_list = account_get_list();
+
+  if(!notify_config.trayicon_enabled)
+    return FALSE;
+
+       menu = gtk_ui_manager_get_widget(gtkut_ui_manager(), "/Menus/SysTrayiconPopup/EmailAcc");
+       gtk_widget_show(menu);
+
+       gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu), NULL);
+       submenu = gtk_menu_new();
+
+  for(cur_ac = account_list; cur_ac != NULL; cur_ac = cur_ac->next) {
+    ac_prefs = (PrefsAccount *)cur_ac->data;
+
+    menuitem = gtk_menu_item_new_with_label
+      (ac_prefs->account_name ? ac_prefs->account_name
+       : _("Untitled"));
+    gtk_widget_show(menuitem);
+               gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
+    g_signal_connect(G_OBJECT(menuitem), "activate",
+                    G_CALLBACK(trayicon_compose_acc_cb),
+                    ac_prefs);
+  }
+       gtk_widget_show(submenu);
+       gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu), submenu);
+  return FALSE;
+}
+
+static GdkPixbuf* notification_trayicon_create(void)
+{
+  GdkPixbuf *trayicon_nomail;
+       GtkActionGroup *action_group;
+
+  trayicon_nomail = notification_pixbuf_get(NOTIFICATION_TRAYICON_NOMAIL);
+
+  notification_trayicon_destroy();
+
+  trayicon = gtk_status_icon_new_from_pixbuf(trayicon_nomail);
+
+  g_signal_connect(G_OBJECT(trayicon), "activate",
+                  G_CALLBACK(notification_trayicon_on_activate), NULL);
+  g_signal_connect(G_OBJECT(trayicon), "popup-menu",
+                  G_CALLBACK(notification_trayicon_on_popup_menu), NULL);
+  g_signal_connect(G_OBJECT(trayicon), "size-changed",
+                  G_CALLBACK(notification_trayicon_on_size_changed), NULL);
+
+  /* Popup-Menu */
+       action_group = cm_menu_create_action_group("SysTrayiconPopup", trayicon_popup_menu_entries,
+                                                                                                                                                                                G_N_ELEMENTS(trayicon_popup_menu_entries), NULL);
+       gtk_action_group_add_toggle_actions(action_group, trayicon_popup_toggle_menu_entries,
+                                                                                                                                                       G_N_ELEMENTS(trayicon_popup_toggle_menu_entries), NULL);
+
+       MENUITEM_ADDUI("/Menus", "SysTrayiconPopup", "SysTrayiconPopup", GTK_UI_MANAGER_MENU)
+       MENUITEM_ADDUI("/Menus/SysTrayiconPopup", "GetMail", "SysTrayiconPopup/GetMail", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI("/Menus/SysTrayiconPopup", "Separator1", "SysTrayiconPopup/---", GTK_UI_MANAGER_SEPARATOR)
+       MENUITEM_ADDUI("/Menus/SysTrayiconPopup", "Email", "SysTrayiconPopup/Email", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI("/Menus/SysTrayiconPopup", "EmailAcc", "SysTrayiconPopup/EmailAcc", GTK_UI_MANAGER_MENU)
+       MENUITEM_ADDUI("/Menus/SysTrayiconPopup", "Separator2", "SysTrayiconPopup/---", GTK_UI_MANAGER_SEPARATOR)
+       MENUITEM_ADDUI("/Menus/SysTrayiconPopup", "OpenAB", "SysTrayiconPopup/OpenAB", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI("/Menus/SysTrayiconPopup", "Separator3", "SysTrayiconPopup/---", GTK_UI_MANAGER_SEPARATOR)
+       MENUITEM_ADDUI("/Menus/SysTrayiconPopup", "ToggleOffline", "SysTrayiconPopup/ToggleOffline", GTK_UI_MANAGER_MENUITEM)
+#ifdef HAVE_LIBNOTIFY
+       MENUITEM_ADDUI("/Menus/SysTrayiconPopup", "ShowBubbles", "SysTrayiconPopup/ShowBubbles", GTK_UI_MANAGER_MENUITEM)
+#endif
+       MENUITEM_ADDUI("/Menus/SysTrayiconPopup", "Separator4", "SysTrayiconPopup/---", GTK_UI_MANAGER_SEPARATOR)
+       MENUITEM_ADDUI("/Menus/SysTrayiconPopup", "Exit", "SysTrayiconPopup/Exit", GTK_UI_MANAGER_MENUITEM)
+
+       traymenu_popup = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
+                               gtk_ui_manager_get_widget(gtkut_ui_manager(), "/Menus/SysTrayiconPopup")));
+
+
+  return trayicon_nomail;
+}
+
+void notification_trayicon_on_activate(GtkStatusIcon *status_icon, gpointer user_data)
+{
+  notification_toggle_hide_show_window();
+}
+
+static void notification_trayicon_on_popup_menu(GtkStatusIcon *status_icon,
+                                               guint button, guint activate_time,
+                                               gpointer user_data)
+{
+  MainWindow *mainwin = mainwindow_get_mainwindow();
+
+  if(!mainwin)
+    return;
+
+  /* tell callbacks to skip any event */
+  updating_menu = TRUE;
+  /* initialize checkitems according to current states */
+       cm_toggle_menu_set_active("SysTrayiconPopup/ToggleOffline", prefs_common_get_prefs()->work_offline);
+#ifdef HAVE_LIBNOTIFY
+       cm_toggle_menu_set_active("SysTrayiconPopup/ShowBubbles", notify_config.trayicon_popup_enabled);
+#endif
+       cm_menu_set_sensitive("SysTrayiconPopup/GetMail", mainwin->lock_count == 0);
+
+  updating_menu = FALSE;
+
+#ifndef G_OS_WIN32
+  gtk_menu_popup(GTK_MENU(traymenu_popup), NULL, NULL, NULL, NULL,
+                button, activate_time);
+#else
+  /* http://bugzilla.gnome.org/show_bug.cgi?id=552642 */
+  gtk_menu_popup(GTK_MENU(traymenu_popup), NULL, NULL, NULL, NULL,
+                0, activate_time);
+#endif
+}
+
+static gboolean notification_trayicon_on_size_changed(GtkStatusIcon *icon,
+                                                     gint size,
+                                                     gpointer user_data)
+{
+  notification_update_msg_counts(NULL);
+  return FALSE;
+}
+
+/* popup menu callbacks */
+static void trayicon_get_all_cb(GtkAction *action, gpointer data)
+{
+  MainWindow *mainwin = mainwindow_get_mainwindow();
+  inc_all_account_mail_cb(mainwin, 0, NULL);
+}
+
+static void trayicon_compose_cb(GtkAction *action, gpointer data)
+{
+  MainWindow *mainwin = mainwindow_get_mainwindow();
+  compose_mail_cb(mainwin, 0, NULL);
+}
+
+static void trayicon_compose_acc_cb(GtkMenuItem *menuitem, gpointer data)
+{
+  compose_new((PrefsAccount *)data, NULL, NULL);
+}
+
+static void trayicon_addressbook_cb(GtkAction *action, gpointer data)
+{
+#ifndef USE_NEW_ADDRBOOK
+    addressbook_open(NULL);
+#else
+    GError* error = NULL;
+
+    addressbook_dbus_open(FALSE, &error);
+    if (error) {
+       g_warning("%s", error->message);
+       g_error_free(error);
+    }
+#endif
+}
+
+static void trayicon_toggle_offline_cb(GtkAction *action, gpointer data)
+{
+  /* toggle offline mode if menu checkitem has been clicked */
+  if(!updating_menu) {
+    MainWindow *mainwin = mainwindow_get_mainwindow();
+    main_window_toggle_work_offline(mainwin, !prefs_common_get_prefs()->work_offline, TRUE);
+  }
+}
+
+#ifdef HAVE_LIBNOTIFY
+static void trayicon_toggle_notify_cb(GtkAction *action, gpointer data)
+{
+  if(!updating_menu) {
+    notify_config.trayicon_popup_enabled = !notify_config.trayicon_popup_enabled;
+  }
+}
+#endif
+
+static void app_exit_cb(MainWindow *mainwin, guint action, GtkWidget *widget)
+{
+  if(prefs_common_get_prefs()->confirm_on_exit) {
+    if(alertpanel(_("Exit"), _("Exit Claws Mail?"),
+                 GTK_STOCK_CANCEL, GTK_STOCK_OK,
+                 NULL) != G_ALERTALTERNATE) {
+      return;
+    }
+    manage_window_focus_in(mainwin->window, NULL, NULL);
+  }
+
+  if (prefs_common_get_prefs()->clean_on_exit) {
+    if (!main_window_empty_trash(mainwin, prefs_common_get_prefs()->ask_on_clean, TRUE))
+      return;
+  }
+
+  app_will_exit(NULL, mainwin);
+}
+
+static void trayicon_exit_cb(GtkAction *action, gpointer data)
+{
+  MainWindow *mainwin = mainwindow_get_mainwindow();
+
+  if(mainwin->lock_count == 0) {
+    app_exit_cb(mainwin, 0, NULL);
+  }
+}
+
+#ifdef HAVE_LIBNOTIFY
+static gboolean notification_trayicon_popup_add_msg(MsgInfo *msginfo,
+                                                   NotificationFolderType nftype)
+{
+  gchar *summary;
+  gchar *utf8_str;
+  gboolean retval;
+  GdkPixbuf *pixbuf;
+
+  g_return_val_if_fail(msginfo, FALSE);
+
+  if(!popup.notification)
+    return notification_trayicon_popup_create(msginfo,nftype);
+
+  /* Count messages */
+  notification_trayicon_popup_count_msgs(nftype);
+
+  if(popup.msg_path) {
+    g_free(popup.msg_path);
+    popup.msg_path = NULL;
+  }
+
+  summary  = notification_trayicon_popup_assemble_summary();
+  utf8_str = notification_trayicon_popup_assemble_body(msginfo);
+
+  /* make sure we show a logo on many msg arrival */
+  pixbuf = notification_pixbuf_get(NOTIFICATION_CM_LOGO_64x64);
+  if(pixbuf)
+    notify_notification_set_icon_from_pixbuf(popup.notification, pixbuf);
+
+  retval = notify_notification_update(popup.notification, summary,
+                                     utf8_str, NULL);
+  g_free(summary);
+  g_free(utf8_str);
+  if(!retval) {
+    debug_print("Notification Plugin: Failed to update notification.\n");
+    return FALSE;
+  }
+  /* Show the popup */
+  notify_notification_set_hint_string(popup.notification, "desktop-entry", "claws-mail");
+  if(!notify_notification_show(popup.notification, &(popup.error))) {
+    debug_print("Notification Plugin: Failed to send updated notification: "
+               "%s\n", popup.error->message);
+    g_clear_error(&(popup.error));
+    return FALSE;
+  }
+
+  debug_print("Notification Plugin: Popup successfully modified "
+             "with libnotify.\n");
+
+  return TRUE;
+}
+
+static gboolean notification_trayicon_popup_create(MsgInfo *msginfo,
+                                                  NotificationFolderType nftype)
+{
+  gchar *summary = NULL;
+  gchar *utf8_str = NULL;
+  GdkPixbuf *pixbuf;
+  GList *caps = NULL;
+  gboolean support_actions = FALSE;
+
+  /* init libnotify if necessary */
+  if(!notify_is_initted()) {
+    if(!notify_init("claws-mail")) {
+      debug_print("Notification Plugin: Failed to initialize libnotify. "
+                 "No popups will be shown.\n");
+      return FALSE;
+    }
+  }
+
+  /* Count messages */
+  notification_trayicon_popup_count_msgs(nftype);
+
+  summary  = notification_trayicon_popup_assemble_summary();
+  utf8_str = notification_trayicon_popup_assemble_body(msginfo);
+
+#if NOTIFY_CHECK_VERSION(0, 7, 0)
+  popup.notification = notify_notification_new(summary, utf8_str, NULL);
+#else
+  popup.notification = notify_notification_new(summary, utf8_str, NULL, NULL);
+  notify_notification_attach_to_status_icon(popup.notification, trayicon);
+#endif
+
+  g_free(summary);
+  g_free(utf8_str);
+
+  caps = notify_get_server_caps();
+    if(caps != NULL) {
+      GList *c;
+      for(c = caps; c != NULL; c = c->next) {
+       if(strcmp((char*)c->data, "actions") == 0 ) {
+         support_actions = TRUE;
+         break;
+        }
+      }
+
+    g_list_foreach(caps, (GFunc)g_free, NULL);
+    g_list_free(caps);
+  }
+
+  /* Default action */
+  if (support_actions)
+    notify_notification_add_action(popup.notification,
+                                  "default", "Present main window",
+                                  (NotifyActionCallback)
+                                  notification_trayicon_popup_default_action_cb,
+                                  GINT_TO_POINTER(nftype),
+                                  notification_trayicon_popup_free_func);
+
+  if(popup.notification == NULL) {
+    debug_print("Notification Plugin: Failed to create a new notification.\n");
+    return FALSE;
+  }
+
+  /* Icon */
+  pixbuf = NULL;
+#ifndef USE_NEW_ADDRBOOK
+  if(msginfo && msginfo->from) {
+    gchar *icon_path;
+    icon_path = addrindex_get_picture_file(msginfo->from);
+    if(is_file_exist(icon_path)) {
+      GError *error = NULL;
+      gint w, h;
+
+      gdk_pixbuf_get_file_info(icon_path, &w, &h);
+      if((w > 64) || (h > 64))
+       pixbuf = gdk_pixbuf_new_from_file_at_scale(icon_path,
+                                                  64, 64, TRUE, &error);
+      else
+       pixbuf = gdk_pixbuf_new_from_file(icon_path, &error);
+
+      if(!pixbuf) {
+       debug_print("Could not load picture file: %s\n",
+                   error ? error->message : "no details");
+       g_error_free(error);
+      }
+    }
+    else
+      debug_print("Picture path does not exist: %s\n",icon_path);
+    g_free(icon_path);
+  }
+#endif
+  if(!pixbuf)
+    pixbuf = g_object_ref(notification_pixbuf_get(NOTIFICATION_CM_LOGO_64x64));
+
+  if(pixbuf) {
+    notify_notification_set_icon_from_pixbuf(popup.notification, pixbuf);
+    g_object_unref(pixbuf);
+  }
+  else /* This is not fatal */
+    debug_print("Notification plugin: Icon could not be loaded.\n");
+
+  /* timeout */
+  notify_notification_set_timeout(popup.notification, notify_config.trayicon_popup_timeout);
+
+  /* Category */
+  notify_notification_set_category(popup.notification, "email.arrived");
+
+  /* get notified on bubble close */
+  g_signal_connect(G_OBJECT(popup.notification), "closed", G_CALLBACK(popup_timeout_fun), NULL);
+
+  /* Show the popup */
+  notify_notification_set_hint_string(popup.notification, "desktop-entry", "claws-mail");
+  if(!notify_notification_show(popup.notification, &(popup.error))) {
+    debug_print("Notification Plugin: Failed to send notification: %s\n",
+               popup.error->message);
+    g_clear_error(&(popup.error));
+    g_object_unref(G_OBJECT(popup.notification));
+    popup.notification = NULL;
+    return FALSE;
+  }
+
+  /* Store path to message */
+  if(nftype == F_TYPE_MAIL) {
+    if(msginfo->folder && msginfo->folder) {
+      gchar *ident;
+      ident = folder_item_get_identifier(msginfo->folder);
+      popup.msg_path = g_strdup_printf("%s%s%u", ident,G_DIR_SEPARATOR_S,
+                                      msginfo->msgnum);
+      g_free(ident);
+    }
+    else
+      popup.msg_path = NULL;
+  }
+
+  debug_print("Notification Plugin: Popup created with libnotify.\n");
+
+  return TRUE;
+}
+
+static void popup_timeout_fun(NotifyNotification *nn, gpointer data)
+{
+  G_LOCK(trayicon_popup);
+
+  g_object_unref(G_OBJECT(popup.notification));
+
+  popup.notification = NULL;
+  g_clear_error(&(popup.error));
+
+  popup.count = 0;
+  popup.num_mail = 0;
+  popup.num_news = 0;
+  popup.num_calendar = 0;
+  popup.num_rss = 0;
+
+  if(popup.msg_path) {
+    g_free(popup.msg_path);
+    popup.msg_path = NULL;
+  }
+
+  G_UNLOCK(trayicon_popup);
+}
+
+static void notification_trayicon_popup_free_func(gpointer data)
+{
+  if(popup.msg_path) {
+    g_free(popup.msg_path);
+    popup.msg_path = NULL;
+  }
+
+  debug_print("Freed notification data\n");
+}
+
+static void notification_trayicon_popup_default_action_cb(NotifyNotification
+                                                         *notification,
+                                                         const char *action,
+                                                         void *user_data)
+{
+  if(strcmp("default", action))
+    return;
+
+  MainWindow *mainwin;
+  mainwin = mainwindow_get_mainwindow();
+  if(mainwin) {
+    NotificationFolderType nftype;
+
+    /* Let mainwindow pop up */
+    notification_show_mainwindow(mainwin);
+    /* If there is only one new mail message, jump to this message */
+    nftype = (NotificationFolderType)GPOINTER_TO_INT(user_data);
+    if((popup.count == 1) && (nftype == F_TYPE_MAIL)) {
+      gchar *select_str;
+      G_LOCK(trayicon_popup);
+      select_str = g_strdup(popup.msg_path);
+      G_UNLOCK(trayicon_popup);
+      debug_print("Notification plugin: Select message %s\n", select_str);
+      mainwindow_jump_to(select_str, FALSE);
+      g_free(select_str);
+    }
+  }
+}
+
+static void notification_trayicon_popup_count_msgs(NotificationFolderType nftype)
+{
+  switch(nftype) {
+  case F_TYPE_MAIL:
+    popup.num_mail++;
+    break;
+  case F_TYPE_NEWS:
+    popup.num_news++;
+    break;
+  case F_TYPE_CALENDAR:
+    popup.num_calendar++;
+    break;
+  case F_TYPE_RSS:
+    popup.num_rss++;
+    break;
+  default:
+    debug_print("Notification plugin: Unknown folder type\n");
+    return;
+  }
+  popup.count++;
+}
+
+/* The returned value has to be freed by the caller */
+static gchar* notification_trayicon_popup_assemble_summary(void)
+{
+  gchar *summary = NULL;
+
+  if(popup.count == 1) {
+    if(popup.num_mail)
+      summary = g_strdup(_("New mail message"));
+    else if(popup.num_news)
+      summary = g_strdup(_("New news post"));
+    else if(popup.num_calendar)
+      summary = g_strdup(_("New calendar message"));
+    else
+      summary = g_strdup(_("New article in RSS feed"));
+  } /* One new message */
+  else {
+    summary = g_strdup(_("New messages arrived"));
+  } /* Many new messages */
+
+  return summary;
+}
+
+/* The returned value has to be freed by the caller */
+static gchar* notification_trayicon_popup_assemble_body(MsgInfo *msginfo)
+{
+  gchar *utf8_str;
+
+  if(popup.count == 1) {
+    if(popup.num_mail || popup.num_news) {
+      gchar *from;
+      gchar *subj;
+      gchar *text;
+         gchar *foldname = NULL;
+
+      from = notification_libnotify_sanitize_str(msginfo->from ?
+                                                msginfo->from :
+                                                _("(No From)"));
+      subj = notification_libnotify_sanitize_str(msginfo->subject ?
+                                                msginfo->subject :
+                                                _("(No Subject)"));
+       if (notify_config.trayicon_display_folder_name) {
+        foldname = notification_libnotify_sanitize_str(msginfo->folder->path);
+        text = g_strconcat(from,"\n\n", subj, "\n\n", foldname, NULL);
+       }
+    else
+        text = g_strconcat(from, "\n\n",subj, NULL);
+
+
+      /* Make sure text is valid UTF8 */
+      utf8_str = notification_validate_utf8_str(text);
+      g_free(text);
+
+      if(from) g_free(from);
+      if(subj) g_free(subj);
+         if(foldname) g_free(foldname);
+    }
+    else if(popup.num_calendar) {
+      utf8_str = g_strdup(_("A new calendar message arrived"));
+    }
+    else {
+      utf8_str = g_strdup(_("A new article in a RSS feed arrived"));
+    }
+  } /* One message */
+
+  else {
+    gchar *msg;
+    gchar *tmp;
+    gboolean str_empty = TRUE;
+
+    utf8_str = g_strdup("");
+
+    if(popup.num_mail) {
+      msg = g_strdup_printf(ngettext("%d new mail message arrived",
+                                    "%d new mail messages arrived",
+                           popup.num_mail),
+                           popup.num_mail);
+      tmp = g_strdup_printf("%s%s%s",utf8_str,str_empty?"":"\n",msg);
+      g_free(msg);
+      g_free(utf8_str);
+      utf8_str = tmp;
+      str_empty = FALSE;
+    }
+    if(popup.num_news) {
+      msg = g_strdup_printf(ngettext("%d new news post arrived",
+                                    "%d new news posts arrived",
+                           popup.num_news),
+                           popup.num_news);
+      tmp = g_strdup_printf("%s%s%s",utf8_str,str_empty?"":"\n",msg);
+      g_free(msg);
+      g_free(utf8_str);
+      utf8_str = tmp;
+      str_empty = FALSE;
+    }
+    if(popup.num_calendar) {
+      msg = g_strdup_printf(ngettext("%d new calendar message arrived",
+                                    "%d new calendar messages arrived",
+                           popup.num_calendar),
+                           popup.num_calendar);
+      tmp = g_strdup_printf("%s%s%s",utf8_str,str_empty?"":"\n",msg);
+      g_free(msg);
+      g_free(utf8_str);
+      utf8_str = tmp;
+      str_empty = FALSE;
+    }
+    if(popup.num_rss) {
+      msg = g_strdup_printf(ngettext("%d new article in RSS feeds arrived",
+                                    "%d new articles in RSS feeds arrived",
+                           popup.num_rss),
+                           popup.num_rss);
+      tmp = g_strdup_printf("%s%s%s",utf8_str,str_empty?"":"\n",msg);
+      g_free(msg);
+      g_free(utf8_str);
+      utf8_str = tmp;
+      str_empty = FALSE;
+    }
+  } /* Many messages */
+
+  return utf8_str;
+}
+
+#endif /* HAVE_LIBNOTIFY */
+
+gboolean notification_trayicon_is_available(void)
+{
+       gboolean is_available;
+       is_available = FALSE;
+
+       if(trayicon) {
+               if(gtk_status_icon_is_embedded(trayicon) &&
+                        gtk_status_icon_get_visible(trayicon))
+                       is_available = TRUE;
+       }
+
+       return is_available;
+}
+
+#endif /* NOTIFICATION_TRAYICON */
diff --git a/src/plugins/notification/notification_trayicon.h b/src/plugins/notification/notification_trayicon.h
new file mode 100644 (file)
index 0000000..ff988a3
--- /dev/null
@@ -0,0 +1,47 @@
+/* Notification plugin for Claws-Mail
+ * Copyright (C) 2005-2007 Holger Berndt and the Claws Mail Team.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NOTIFICATION_TRAYICON_H
+#define NOTIFICATION_TRAYICON_H NOTIFICATION_TRAYICON_H
+
+#include "pluginconfig.h"
+
+#ifdef NOTIFICATION_TRAYICON
+
+#include <glib.h>
+
+#include "procmsg.h"
+
+#define TRAYICON_SPECIFIC_FOLDER_ID_STR "trayicon"
+
+void notification_trayicon_msg(MsgInfo*);
+
+void notification_trayicon_destroy(void);
+
+/* creates it, if necessary */
+void notification_update_trayicon(void);
+
+gboolean notification_trayicon_main_window_close(gpointer, gpointer);
+gboolean notification_trayicon_main_window_got_iconified(gpointer, gpointer);
+gboolean notification_trayicon_account_list_changed(gpointer, gpointer);
+
+gboolean notification_trayicon_is_available(void);
+void notification_trayicon_on_activate(GtkStatusIcon*,gpointer);
+
+#endif /* NOTIFICATION_TRAYICON */
+
+#endif /* include guard */
diff --git a/src/plugins/notification/placeholder.txt b/src/plugins/notification/placeholder.txt
deleted file mode 100644 (file)
index 3b94f91..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Placeholder
diff --git a/src/plugins/notification/plugin.def b/src/plugins/notification/plugin.def
new file mode 100644 (file)
index 0000000..8916a5d
--- /dev/null
@@ -0,0 +1,10 @@
+EXPORTS
+        plugin_desc
+        plugin_done
+        plugin_init
+        plugin_licence
+        plugin_name
+        plugin_type
+       plugin_provides
+        plugin_version
+
diff --git a/src/plugins/notification/raw_claws_mail_logo_64x64.h b/src/plugins/notification/raw_claws_mail_logo_64x64.h
new file mode 100644 (file)
index 0000000..0d51522
--- /dev/null
@@ -0,0 +1,684 @@
+/* GdkPixbuf RGBA C-Source image dump */
+
+#ifdef __SUNPRO_C
+#pragma align 4 (raw_claws_mail_logo_64x64)
+#endif
+#ifdef __GNUC__
+static const guint8 raw_claws_mail_logo_64x64[] __attribute__ ((__aligned__ (4))) = 
+#else
+static const guint8 raw_claws_mail_logo_64x64[] = 
+#endif
+{ ""
+  /* Pixbuf magic (0x47646b50) */
+  "GdkP"
+  /* length: header (24) + pixel_data (16384) */
+  "\0\0@\30"
+  /* pixdata_type (0x1010002) */
+  "\1\1\0\2"
+  /* rowstride (256) */
+  "\0\0\1\0"
+  /* width (64) */
+  "\0\0\0@"
+  /* height (64) */
+  "\0\0\0@"
+  /* pixel_data: */
+  "\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\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\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\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\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\1\0\0\0\1\0\0\0\3\0\0\0\5\0\0\0\7\0\0\0\10\0\0\0\10\0\0\0\7\0"
+  "\0\0\6\0\0\0\4\0\0\0\2\0\0\0\1\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\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\1\0"
+  "\0\0\1\0\0\0\3\0\0\0\6\0\0\0\15\0\0\0\27\0\0\0(\0\0\0=\15\30\35W\"@M"
+  "w-Sc\2073]n\2226`q\2250Ve\214)IW}\26&-_\4\6\7B\0\0\0+\0\0\0\31\0\0\0"
+  "\16\0\0\0\6\0\0\0\2\0\0\0\1\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\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\1\0\0\0\2\0\0\0\4\0\0\0\14\0\0\0\31\0\0\0""0\24&.c8g{\244T\215\245"
+  "\321s\257\310\362\215\304\333\375\243\321\344\377\261\327\350\377\271"
+  "\333\352\377\273\335\352\377\265\331\350\377\252\323\346\377\227\311"
+  "\337\376\200\267\316\366b\230\260\335Gw\213\263&@Kv\4\4\4""7\0\0\0\31"
+  "\0\0\0\12\0\0\0\4\0\0\0\1\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\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\4\0\0\0\14\0\0"
+  "\0\40\0\5\13P\25""7P\242Y\225\255\333\211\302\333\375\265\330\350\377"
+  "\321\343\355\377\343\354\362\377\346\354\361\377\353\362\365\377\360"
+  "\367\372\377\360\367\372\377\360\367\372\377\360\367\372\377\360\367"
+  "\372\377\360\366\372\377\353\364\370\377\336\356\364\377\304\340\355"
+  "\377\235\315\340\376o\246\275\351Fr\204\254\20\30\34O\0\0\0\37\0\0\0"
+  "\12\0\0\0\3\0\0\0\1\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\0\0\0\1\0\0\0\3\0\0\0\10\0\0\0\26\0\2\4C\22""4N\250:g\212\361\236"
+  "\304\327\377\311\334\346\377\331\341\350\377\336\344\352\377\336\344"
+  "\352\377\336\344\352\377\337\346\353\377\346\356\363\377\345\354\361"
+  "\377\344\353\357\377\344\353\357\377\346\357\363\377\352\363\370\377"
+  "\353\364\370\377\351\363\367\377\346\355\363\377\352\363\366\377\352"
+  "\364\370\377\334\354\364\377\265\332\350\377~\264\314\365Jv\212\263\14"
+  "\21\24H\0\0\0\30\0\0\0\7\0\0\0\2\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\1\0\0\0\4\0\0\0\15\0\0\0-\22)6zCz\227\337\220\271\320\377\316"
+  "\340\352\377\325\336\346\377\324\334\344\377\324\334\344\377\324\334"
+  "\344\377\324\334\344\377\325\334\344\377\325\335\344\377\326\335\345"
+  "\377\326\335\345\377\326\336\345\377\327\336\346\377\327\336\346\377"
+  "\330\340\346\377\331\342\350\377\330\337\346\377\330\337\346\377\330"
+  "\340\347\377\335\345\354\377\340\352\360\377\344\357\365\377\334\355"
+  "\364\377\260\326\346\377r\251\300\3555Tb\214\0\0\0*\0\0\0\13\0\0\0\3"
+  "\0\0\0\1\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\2\0\0\0\5\0\0\0\21\0\0\0""3\40G_\257i\234"
+  "\272\376\242\270\313\377\306\321\334\377\312\324\336\377\312\324\336"
+  "\377\312\324\336\377\312\324\336\377\312\324\336\377\313\325\336\377"
+  "\314\325\337\377\314\326\337\377\315\326\337\377\315\327\340\377\316"
+  "\327\340\377\316\327\340\377\317\330\341\377\317\330\341\377\317\330"
+  "\341\377\320\331\341\377\320\331\342\377\320\331\342\377\320\331\342"
+  "\377\320\331\342\377\320\331\342\377\326\342\351\377\334\352\361\377"
+  "\313\342\354\377\220\303\330\375Q\200\223\276\6\11\12@\0\0\0\22\0\0\0"
+  "\5\0\0\0\1\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\2\0\0\0\6\0\0\0\27\0\5\12N\13/O\303Sz\234\376\262\301\320"
+  "\377\277\314\330\377\277\314\330\377\277\314\330\377\277\314\330\377"
+  "\277\314\330\377\277\314\330\377\300\315\330\377\301\315\331\377\302"
+  "\316\331\377\303\317\332\377\303\317\332\377\276\312\324\377\266\302"
+  "\314\377\302\316\327\377\200\207\216\377\277\313\325\377\307\321\334"
+  "\377\307\321\335\377\307\322\335\377\310\322\335\377\306\320\333\377"
+  "\310\322\335\377\310\322\335\377\310\322\335\377\310\322\334\377\310"
+  "\322\334\377\300\315\331\377\227\273\320\377T\204\235\334\25\37$`\0\0"
+  "\0\36\0\0\0\6\0\0\0\1\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\2\0\0\0\6\0\0\0\27\0\7\16W\17""8`\344j\210\244\377\255\275\315"
+  "\377\265\304\322\377\265\304\322\377\265\304\322\377\265\304\322\377"
+  "\265\304\322\377\265\304\322\377\266\305\322\377\267\305\323\377\270"
+  "\306\324\377\271\307\324\377\270\306\323\377\226\240\253\377\201\213"
+  "\224\377py\200\377\267\304\320\377\40\"#\377\216\225\233\377\304\317"
+  "\331\377\275\310\325\377\275\310\324\377\237\251\264\377\203\213\224"
+  "\377\257\272\306\377\300\314\330\377\300\314\327\377\300\314\327\377"
+  "\300\314\330\377\300\313\327\377\274\311\325\377\207\236\263\377T\200"
+  "\236\372\32-=\206\0\0\0\33\0\0\0\6\0\0\0\1\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\2\0\0\0\6\0\0\0\26\0\3\10O\13""2Z\334n\213\247\377\251\272"
+  "\313\377\252\274\314\377\252\274\314\377\252\274\314\377\252\274\314"
+  "\377\252\273\314\377\252\273\314\377\253\275\314\377\254\275\315\377"
+  "\255\276\316\377\256\277\316\377\260\300\317\377\252\271\307\377^fn\377"
+  "(,/\377ox\202\377\240\256\272\377!$&\377\26\27\30\377\216\223\230\377"
+  "\235\247\257\377dkq\377\32\34\36\377r{\203\377lt{\377\237\253\266\377"
+  "\271\307\324\377\270\305\323\377\272\310\326\377\270\305\322\377\267"
+  "\305\322\377\267\304\322\377\253\304\325\377[\212\246\362\14\26\36d\0"
+  "\0\0\26\0\0\0\4\0\0\0\1\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\1\0\0\0\5\0\0\0\24\0\4\10"
+  "K\12""1X\333_~\236\377\236\260\304\377\240\263\306\377\240\263\306\377"
+  "\240\263\306\377\240\263\306\377\240\263\306\377\240\263\306\377\240"
+  "\263\306\377\242\264\307\377\243\265\310\377\244\266\310\377\243\265"
+  "\305\377\233\253\273\377\240\260\301\377x\204\220\377\24\26\30\377|\210"
+  "\225\377\203\215\231\377\34\36\40\377\0\0\0\377\36\37\37\377ORU\377\3"
+  "\3\3\377\16\17\21\377\26\30\31\377_in\377\265\304\323\377\277\323\337"
+  "\377\267\310\326\377\304\331\344\377\261\277\316\377\262\301\317\377"
+  "\300\325\341\377\311\343\355\377\260\325\344\377Q~\232\351\6\11\14J\0"
+  "\0\0\17\0\0\0\3\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\1\0\0\0\4\0\0\0\15\0\0\0""1\4$D\273Tv\227"
+  "\377\223\250\276\377\226\253\300\377\226\253\300\377\226\253\300\377"
+  "\226\253\300\377\226\253\300\377\226\253\300\377\226\253\300\377\227"
+  "\254\300\377\231\255\301\377\232\256\302\377\234\257\303\377\233\257"
+  "\302\377\212\232\253\377q\177\214\3777>D\377\37\"%\377`ir\377mu}\377"
+  "\27\31\33\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\21\23"
+  "\24\377\207\231\241\377\276\327\342\377\311\344\357\377\311\344\357\377"
+  "\260\303\321\377\252\272\312\377\260\302\321\377\310\342\355\377\306"
+  "\341\355\377\305\337\353\377\241\314\340\377>f~\311\5\5\5""3\0\0\0\12"
+  "\0\0\0\2\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\2\0\0\0\11\0\0\0\36\0\17\37z&Qz\374\204\234\265\377\214"
+  "\242\271\377\214\242\271\377\214\242\271\377\214\242\271\377\214\242"
+  "\272\377\214\242\272\377\214\242\272\377\214\242\272\377\216\244\273"
+  "\377\220\245\274\377\221\246\275\377\223\250\276\377\210\235\260\377"
+  "9BJ\377\2\2\2\377\0\0\0\377\20\22\24\377\40#&\377#&)\377\6\7\5\377)9"
+  "\6\377*;\6\377\25\35\3\377\6\11\0\377\0\0\0\377(.1\377s\203\213\377\211"
+  "\234\245\377\215\241\251\377\304\341\355\377\263\312\331\377\244\265"
+  "\307\377\246\271\312\377\302\340\354\377\300\337\353\377\300\336\353"
+  "\377\275\334\352\377\205\267\320\376\35""3D\220\0\0\0\34\0\0\0\5\0\0"
+  "\0\1\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\1\0\0"
+  "\0\5\0\0\0\22\0\0\1C\12""2Z\336c\202\241\377\201\232\263\377\202\232"
+  "\263\377\202\232\263\377\202\232\263\377\202\232\264\377\202\232\264"
+  "\377\202\232\264\377\202\232\264\377\202\233\265\377\203\234\265\377"
+  "}\230\261\377o\215\251\377[|\235\377Gj\216\3775Y}\377\34""3J\377\2\4"
+  "\6\377\0\0\0\377\0\0\0\377\1\2\0\377d\207\21\377\202\262\26\377o\226"
+  "\23\377`\202\21\377F`\12\377\24\35\2\377\0\0\0\377\12\14\15\377.46\377"
+  "z\214\224\377\255\311\325\377\267\324\340\377\236\261\303\377\256\307"
+  "\327\377\276\336\353\377\274\334\351\377\274\334\351\377\273\334\351"
+  "\377\256\324\344\377V\206\242\353\7\10\11E\0\0\0\16\0\0\0\3\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\2\0\0\0\12\0\0\0#\0"
+  "\27-\2224]\205\377t\220\254\377w\222\255\377w\222\255\377w\222\255\377"
+  "w\222\255\377w\222\255\377w\222\256\377v\222\256\377n\213\250\377Vx\233"
+  "\3777`\210\377\37Mz\377\25Ft\377\23Dr\377\27Ft\377\32Ep\377!Kw\377\37"
+  "7O\377\6\12\16\377\0\0\0\377;P\16\377\207\267\34\377q\233\24\377k\222"
+  "\23\377m\225\24\377k\223\23\377Hc\12\377\23\33\1\377\10\13\6\377h~\207"
+  "\377\246\310\327\377\243\304\322\377\270\330\345\377\271\331\347\377"
+  "\273\334\352\377\272\334\351\377\271\333\351\377\270\331\351\377\266"
+  "\331\347\377\264\327\346\377\221\305\334\377>bp\236\0\0\0\37\0\0\0\6"
+  "\0\0\0\1\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\1\0\0\0\5\0\0\0\21"
+  "\0\0\0<\10""0W\333Xz\232\377l\212\246\377l\212\247\377l\212\247\377l"
+  "\212\247\377l\212\247\377k\211\247\377[}\236\3775]\204\377\22:_\377\30"
+  ";L\377/PA\3776P=\377OhC\377RfA\377o\210C\377f{C\377Xk;\377Uh0\377GQ3"
+  "\377La\36\377o\2114\377t\2210\377l\215\"\377h\206&\377h\211\34\377c\204"
+  "\31\377Eb\11\3771F\6\377\27!\2\377\25\32\34\377L]d\377@S\\\377\222\302"
+  "\327\377\237\317\343\377\257\326\347\377\265\331\350\377\264\331\350"
+  "\377\263\327\347\377\261\327\346\377\260\325\345\377\246\320\342\377"
+  "k\243\271\350\4\5\5B\0\0\0\15\0\0\0\3\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\2\0\0\0\11\0\0\0\34\0\15\32t\40Lx\376`\200\240\377b\202\241"
+  "\377b\202\241\377b\202\241\377a\201\240\377Qt\230\377'R{\377\35>F\377"
+  "6I/\377y\222C\377\200\231G\377\201\234A\377\200\2378\377}\233;\377{\232"
+  "7\377o\2106\377f}3\377e\201%\377\\t&\377e\200+\377s\224'\377y\2320\377"
+  "h\210\40\377b\177\33\377c\204\32\377h\210#\377f\210\33\377M_)\377FZ\33"
+  "\377$/\24\377k\212\230\3775IR\377#29\377\177\264\313\377\207\303\334"
+  "\377\212\303\333\377\230\312\337\377\251\323\344\377\256\325\346\377"
+  "\255\324\345\377\254\323\343\377\251\321\343\377\211\301\331\3778Xe\220"
+  "\0\0\0\32\0\0\0\5\0\0\0\1\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\3\0\0\0\15"
+  "\0\0\0+\2#C\270:b\210\377Xz\232\377Xz\232\377Wy\232\377Nr\226\377*V\200"
+  "\377\7""6c\377\20\34(\377q\204G\377\224\271=\377\205\2516\377y\241\""
+  "\377y\235%\377t\223/\377k\211'\377O_(\377Ma\40\377IY(\377IZ#\377e}-\377"
+  "f\204\"\377k\216\34\377k\213%\377_~\31\377Vn\36\377b\203\32\377l\222"
+  "\26\377y\234*\377[o.\377d~*\377c~*\377XmT\377t\244\270\377\211\300\330"
+  "\377\213\304\335\377\210\303\334\377\206\302\334\377\204\300\331\377"
+  "\206\301\332\377\225\310\336\377\245\320\342\377\246\321\342\377\245"
+  "\317\341\377\230\311\335\377[\216\244\324\0\0\0""0\0\0\0\12\0\0\0\2\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\6\0\0\0\23\0\0\0\77\15""8_\342Dj\216"
+  "\377Nr\224\377Lp\224\3775^\206\377\16\77p\377\0""1`\377QRJ\377\306\264"
+  "\223\3771*%\377Sh#\377j\217\27\377e\206\31\377f\210\30\377Pe\40\377F"
+  "U&\377>L#\377N[/\377g~3\377\214\257<\377f\201'\377i\213\32\377g\203("
+  "\377a|$\377Ob&\377EX\34\377[w\32\377l\221\26\377\205\2520\377o\205>\377"
+  "e\200,\377z\236'\377|\2326\377Nb1\377i\222\250\377\215\306\337\377\212"
+  "\304\335\377\210\303\334\377\204\300\332\377\202\277\331\377\177\276"
+  "\327\377\205\300\330\377\226\310\335\377\237\314\337\377\234\313\336"
+  "\377w\263\315\372\22\30\33X\0\0\0\20\0\0\0\3\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\11\0\0\0\34\1\10\17d\24Dq\374Ag\214\377>d\212\377\36Kx\377"
+  "\3""6i\377\0""4g\377)9A\377\362\352\326\377\325\275\214\377pbH\377\77"
+  "H/\377Vi*\377Na#\377CP%\377\40'\21\377!,\40\377\27#\35\377`u1\377\227"
+  "\265N\377}\230@\377o\2121\377e\201'\377f\205\37\377K^!\377>L\34\377C"
+  "T\36\377[|\22\377k\213&\377\210\250<\377u\2179\377e\203\"\377e\201&\377"
+  "n\215,\377v\221:\377\\p>\377w\247\271\377\214\305\335\377\210\303\334"
+  "\377\206\301\333\377\202\277\331\377\177\276\327\377}\274\326\377|\273"
+  "\325\377\210\300\330\377\225\310\334\377\203\276\326\3777Vc\221\0\0\0"
+  "\32\0\0\0\5\0\0\0\1\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\14\0\0\0%\0\34""6\237\40"
+  "Ly\377/Y\202\377\16\77o\377\0""4g\377\0""4h\377\0""1a\377\220\205l\377"
+  "\353\341\310\377\316\265\200\377H@1\377+3\36\377=K\36\3778D\33\377)4"
+  "#\377\12(E\377\25*4\377i\200:\377\224\270@\377{\227;\377`s2\377Ym.\377"
+  "Pd\40\377I[!\377\77G,\377,5\30\377BT\36\377Xr\36\377s\223*\377\216\256"
+  "D\377at6\377Qd'\377Zv\32\377Ti$\377x\220B\377\242\303S\377cxC\377p\235"
+  "\261\377\210\303\334\377\206\301\333\377\202\277\331\377\200\276\330"
+  "\377}\275\326\377z\272\325\377w\270\323\377~\272\324\377\202\276\326"
+  "\377O\177\222\300\0\0\0&\0\0\0\10\0\0\0\1\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\20"
+  "\0\0\0/\1$H\302\30Ft\377\7""9j\377\0""4g\377\0""4g\377\0""5h\377\0/\\"
+  "\377\277\260\220\377\335\312\243\377d\\G\377\0\31""2\377\26\35\34\377"
+  "#+\22\377*0\35\377\14!.\377#6/\377\203\242>\377}\230@\377w\2253\377p"
+  "\220(\377`y#\377Pi\35\377FV#\377*;\32\377\0\1\0\377\32\40\37\377<L\32"
+  "\377Tr\22\377s\2204\377\200\231E\377Yo'\377%,\32\377IY&\377Un\37\377"
+  "Zs\"\377\220\263\77\377\216\256D\377YqI\377v\252\300\377\205\301\333"
+  "\377\202\277\331\377\177\276\330\377}\274\326\377z\272\325\377w\270\323"
+  "\377t\266\322\377s\266\322\377[\223\253\337\0\0\0""2\0\0\0\13\0\0\0\2"
+  "\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\23\0\0\0""7\0(P\327\2""5f\377\0""4f\377\0"
+  "4g\377\0""4g\377\0""5h\377\0""1a\377\220\204d\377\326\300\220\377\22"
+  "%4\377\0&L\377\0#G\377\7\"=\377\6\33""1\377,;&\377x\222<\377~\2348\377"
+  "l\212,\377Ys\34\377]q1\3773@\27\3775>\40\377)3\34\377\16\26\34\377\31"
+  "!%\377F]Y\377J]\37\377Wk*\377|\2351\377\211\245G\377r\2138\377\".3\377"
+  ".67\377:F#\377Nk\15\377`{#\377x\231-\377\241\310J\377Vh;\377m\236\256"
+  "\377\201\277\331\377\177\276\330\377}\274\326\377z\272\325\377v\270\322"
+  "\377t\266\322\377p\264\321\377d\242\274\361\3\3\3C\0\0\0\16\0\0\0\3\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\26\0\0\0>\0-V\343\0""4f\377\0""4f\377\0""4g"
+  "\377\0""4h\377\0""5h\377\0""3e\3776==\377\330\276\211\377\37-7\377\0"
+  "&L\377\0#F\377\25,4\377Xj6\377}\2363\377f\205!\377_|\34\377]x!\377Rf"
+  "$\37719!\377\26/D\377\31""8U\377\30""1J\377$D`\377U\200\221\377\77S8"
+  "\377I[!\377Ti'\377f\207\36\377\234\301H\377\232\271M\377Ool\377d\213"
+  "\235\3773E:\377=H%\377Ql\24\377h\205%\377s\221.\377\206\2515\377Pd5\377"
+  "q\246\275\377~\275\330\377{\274\326\377z\271\325\377v\270\322\377t\266"
+  "\322\377p\264\321\377j\255\311\373\11\14\15R\0\0\0\21\0\0\0\4\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\30\0\0\0C\22Go\353\2""7i\377\0""4f\377\0""4g\377\0"
+  "4h\377\0""5h\377\0""1`\377\0!B\377e^J\377\211z\\\377\3\33.\3776D*\377"
+  "x\222>\377t\2158\377o\221\40\377g\206$\377a{(\377J_\37\377IT0\377\32"
+  "-3\377\31:Z\377\33;[\377\36=\\\377&Ge\377Z\214\240\377AU\77\377L`!\377"
+  "^z\35\377g\210\35\377\214\253C\377{\222C\377Vxy\377e\217\240\377Y\177"
+  "\216\377&,$\3773B\17\377Wo#\377_x#\377k\206*\377\227\274@\377UiB\377"
+  "f\230\251\377{\273\325\377x\271\325\377u\267\322\377r\265\321\377p\264"
+  "\321\377k\257\314\375\25\35\"a\0\0\0\23\0\0\0\4\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\31\0\0\0D\34S{\356\30S\202\377\0""4f\377\0""4g\377\0""4g\377\0""5"
+  "h\377\0/]\377\0&K\377\0\"C\377CM,\377gy:\377\214\254A\377y\233-\377c"
+  "\200\"\377_u-\377Zn*\377K^\36\377'2\35\377\22'/\377\23""5U\377\27""8"
+  "X\377\32:Z\377\35=\\\377Dt\223\377u\265\316\377=UE\377Re#\377`x'\377"
+  "e\205\36\377\202\241;\377\211\246D\377Ruz\377c\216\240\377c\216\240\377"
+  "Lkx\377484\377La\37\377_y'\377g\207#\377}\2338\377u\215<\377La<\377v"
+  "\265\316\377w\270\324\377t\267\322\377r\265\321\377n\263\320\377l\261"
+  "\316\376\31'-g\0\0\0\24\0\0\0\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\31\0\0\0E\6"
+  "6c\356P\237\304\377)k\226\377\2""6i\377\0""4g\377\0""5h\377\0""1`\377"
+  "\0$E\3770G)\377w\2253\377\200\237=\377f\203%\377Yq\"\377Nb#\377M`!\377"
+  "Yp(\377.:!\377\15+I\377\21""3V\377\24""6W\377\27""8X\377\32<\\\377Eu"
+  "\223\377z\301\337\377w\272\325\377:M#\377H\\\36\377e\205\33\377p\224"
+  "\40\377\233\302C\377]l8\377a\215\232\377f\225\250\377_\213\235\377`\213"
+  "\236\377A_f\377FT*\377Tj$\377b\177$\377p\223\"\377}\2372\377z\223A\377"
+  "_\221\242\377v\267\324\377s\266\321\377p\264\321\377m\263\320\377i\260"
+  "\315\376\26#'d\0\0\0\24\0\0\0\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\31\0\0\0B\1"
+  ".Z\350\24O~\377a\265\330\377D\217\270\377\2""7j\377\0""4h\377\0""4f\377"
+  "\15';\377Xi2\377;E$\3771<\35\377Mb\35\377J_\34\377ES'\377FT(\377CU\33"
+  "\377.=%\377\14""0S\377\17""2T\377\31@a\377D~\244\377h\254\316\377x\301"
+  "\340\377z\303\340\377Eie\377M^)\377Vk(\377c\201\40\377j\217\32\377u\216"
+  ";\377z\222\77\377^tx\377\217\236\245\377lop\377[u\200\377>RV\377ET#\377"
+  "Na#\377Vp\33\377g\206\"\377r\221.\377m\202=\377V\177\210\377t\267\323"
+  "\377r\266\321\377n\263\320\377l\261\320\377h\254\311\374\14\23\25Z\0"
+  "\0\0\24\0\0\0\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\30\0\0\0:\7""0X\330*l\230\377"
+  "a\265\330\377a\265\331\3771v\240\377\0""4h\377\0""5h\377\2$G\377J@*\377"
+  "p\\8\377TE)\377\40\34\37\377@H0\3779F\36\3774A\27\377=J\37\377\33,(\377"
+  "\34Ff\377<o\211\377e\254\313\377t\300\340\377u\300\340\377v\301\337\377"
+  "^\230\253\377EU&\377IZ%\377]z\34\377k\221\23\377v\231$\377\204\237H\377"
+  "Ra0\377\215\215\214\377\262\262\262\377\276\276\275\377kx}\3778MQ\377"
+  "4@\26\377=M\35\377JZ'\377[v\36\377r\2172\377\212\247D\377\\\204\204\377"
+  "s\266\323\377p\265\321\377m\263\320\377k\261\317\377d\246\303\370\3\4"
+  "\4N\0\0\0\22\0\0\0\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\26\0\0\0""2\20""8X\300"
+  "H\224\273\377`\264\330\377a\265\330\377Z\254\321\377\2""6i\377\0""5h"
+  "\377\1""5e\377\221\204a\377\376\370\356\377\312\260z\377yg>\377\14\15"
+  "\21\3779D\35\3772<\33\377;E%\377\12#7\3772f\205\377c\253\312\377p\277"
+  "\337\377q\274\335\377p\271\330\377u\256\306\377K^T\377=K#\377FT'\377"
+  "Zv\36\377Zn0\377ay/\377w\212F\377bw0\377\263\263\262\377\263\263\262"
+  "\377\375\375\373\377\234\237\240\3777NV\3779F\35\377;K\27\377AN#\377"
+  "m\212)\377\201\2453\377\203\235J\377U~\177\377p\265\321\377n\263\320"
+  "\377l\262\317\377i\260\316\377Y\226\260\350\0\0\0>\0\0\0\20\0\0\0\4\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\23\0\0\0+\27;S\245D\216\265\377`\264\327\377"
+  "a\265\330\377a\265\331\377\32X\206\377\0""4g\377%`\211\377\214\177Z\377"
+  "\370\363\347\377\306\256z\377\301\250u\377\26\24\24\3774;%\3775@\37\377"
+  "\20\37\35\377\5-S\377\14>m\377]\225\262\377\201\251\271\377\230\254\265"
+  "\377\257\263\265\377\300\300\300\377T]@\377IZ(\377[n+\377c}%\377b{)\377"
+  "}\232<\377\217\252L\377T\\A\377\235\235\234\377\371\371\367\377\376\376"
+  "\374\377\332\332\330\377Ry\213\377!*\37\377@N$\377K]$\377\40$\23\377"
+  "&'\37\377OZ4\377Z\221\243\377n\264\321\377m\262\317\377j\261\317\377"
+  "h\257\316\377N\205\234\320\0\0\0""2\0\0\0\16\0\0\0\3\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\20\0\0\0$\26*4wQ\236\303\377_\262\325\377_\262\325\377`\263"
+  "\326\377<\204\254\377\0""4f\377\37Sw\377ohI\377\364\354\335\377\305\255"
+  "{\377\311\261\177\377\21\40(\377\20\35,\377\25$.\3778I[\377it\177\377"
+  "\256\261\263\377\302\302\302\377\310\310\310\377\314\314\314\377\317"
+  "\317\317\377\322\322\322\377[\\W\377563\377\24\25\33\377\26\23\23\377"
+  "4(\32\377\15\16\13\377h~7\377VXP\377\343\343\342\377\376\376\374\377"
+  "\376\376\374\377\372\372\370\377Smy\377Aet\377+<6\377G>+\377\260\242"
+  "\213\377\346\331\276\377\221~S\377V\215\244\377l\261\316\377j\260\316"
+  "\377h\256\314\377e\255\313\377;ev\251\0\0\0'\0\0\0\14\0\0\0\3\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\15\0\0\0\36\1\2\2N1o\223\361\\\255\317\377\\\255\317"
+  "\377]\256\320\377\\\256\317\377\34X\204\377\22Ck\377CSM\377\335\315\250"
+  "\377\327\306\241\377\314\265\200\377SVR\377\200\202\204\377\214\214\214"
+  "\377\217\217\217\377\264\264\264\377\317\317\317\377\322\322\322\377"
+  "\326\326\326\377\331\331\331\377\334\334\334\377\337\337\337\377xxy\377"
+  ",-0\377>4\"\377\270\235d\377\350\327\265\377hW5\377##$\377\232\232\231"
+  "\377\366\366\364\377\376\376\374\377\376\376\374\377\375\375\373\377"
+  "fv{\377Q\200\225\377\77dt\377\235\210]\377\357\346\322\377\332\307\240"
+  "\377\230\203T\377W\216\246\377h\254\310\377f\253\310\377d\252\306\377"
+  "a\250\305\377\32-6v\0\0\0\35\0\0\0\11\0\0\0\2\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\12\0\0\0\30\0\0\0""8(Xs\306Z\250\310\377Z\251\312\377Y\247\310\377"
+  "X\245\305\377f\240\272\377k\212\233\377\\in\377\213wL\377\360\346\322"
+  "\377\313\264\200\377\202tV\377\202\202\202\377\226\226\226\377\233\233"
+  "\233\377\322\322\322\377\334\334\334\377\332\332\332\377\340\340\340"
+  "\377\217\215\207\377\334\334\334\377\352\352\352\377\264\264\264\377"
+  "a[P\377\263\227\\\377\317\271\213\377\360\350\323\377m\\8\377www\377"
+  "\274\274\272\377\362\362\360\377\376\376\374\377\374\374\372\377\371"
+  "\370\366\377\200\203\205\377Jv\210\377NSI\377\317\271\211\377\363\355"
+  "\341\377\310\257x\377a\\C\377\\\232\265\377c\247\303\377`\246\302\377"
+  "_\244\301\377R\216\247\352\0\0\1F\0\0\0\26\0\0\0\7\0\0\0\1\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\7\0\0\0\23\0\0\0(\23*7\207P\230\267\377t\244\270\377"
+  "\242\264\274\377\275\300\302\377\307\307\306\377\273\273\273\377\222"
+  "\222\222\377ca[\377\257\234v\377\335\312\243\377\316\266\203\377~rV\377"
+  "mic\377\207\206\204\377\227\224\217\377\215\204p\377i^F\377\310\310\310"
+  "\377\203pE\377\253\253\251\377\362\362\362\377usp\377\226}F\377\303\252"
+  "t\377\354\341\313\377\244\217f\377\\[X\377\261\261\260\377\237\237\235"
+  "\377rdE\377\223\214\177\377\271\271\267\377\324\324\322\377\205\204\204"
+  "\377STI\377\305\260\200\377\355\343\316\377\317\270\211\377\234\205S"
+  "\3777Wc\377_\241\274\377_\241\275\377\\\240\274\377Z\236\273\3778cu\264"
+  "\0\0\0-\0\0\0\20\0\0\0\5\0\0\0\1\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\4\0\0\0\17"
+  "\0\0\0\37\0\0\0L7l\204\351\242\265\275\377\330\330\327\377\300\300\300"
+  "\377\320\320\320\377\315\315\315\377\235\235\235\377\234\234\234\377"
+  "nmj\377\207wW\377\314\264\200\377\324\273\207\377\324\274\207\377\323"
+  "\272\207\377\323\271\201\377\220{N\377\255\253\247\377\340\340\340\377"
+  "\230~H\377zkJ\377\203|n\377\227\200M\377\304\253u\377\337\316\254\377"
+  "\264\243\202\377ROH\377\251\251\251\377\270\267\266\377\364\363\361\377"
+  "\211\202u\377\250\213P\377\272\241k\377\253\230l\377\300\253{\377\323"
+  "\273\210\377\330\305\233\377\310\256x\377\213uF\3775LR\377Du\211\377"
+  "Z\233\267\377Z\234\267\377X\232\266\377S\223\257\372\20\35\"i\0\0\0\37"
+  "\0\0\0\15\0\0\0\4\0\0\0\1\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\2\0\0\0\12\0\0\0\30"
+  "\0\0\0""2\36""9E\251v\226\244\377\375\375\373\377\354\354\352\377\267"
+  "\267\266\377\265\265\265\377\250\250\250\377\247\247\247\377\252\252"
+  "\252\377\235\235\235\377\220\216\211\377\225\214x\377\227\213r\377\230"
+  "\214u\377\225\220\205\377\326\326\326\377\376\376\376\377\365\365\365"
+  "\377~mF\377\300\243f\377\307\256w\377\314\265\201\377\324\277\217\377"
+  "\241\217g\377QOI\377\240\240\240\377\264\264\263\377\330\327\326\377"
+  "\367\366\364\377\360\357\356\377\243\240\234\377\203uW\377\220zO\377"
+  "\237\211Y\377\231\204V\377wlL\377EMC\3771S`\377<fx\377>l\177\377O\213"
+  "\245\377O\213\244\377N\212\243\3775_q\313\0\0\0I\0\0\0+\0\0\0!\0\0\0"
+  "\23\0\0\0\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\1\0\0\0\6\0\0\0\22\0\0\0$\0\2\2"
+  "_Lp\200\357\356\356\354\377\376\376\374\377\376\376\374\377\334\334\332"
+  "\377\216\216\215\377~~~\377\241\241\241\377\264\264\264\377\310\310\310"
+  "\377\373\373\373\377\374\374\374\377\375\375\375\377\376\376\376\377"
+  "\376\376\376\377\374\374\374\377\373\373\373\377\252\251\247\377\201"
+  "rS\377\234\206X\377\234\207Y\377ujS\377VVU\377\234\234\232\377\263\263"
+  "\261\377\304\303\302\377\363\361\357\377\354\354\352\377\356\354\353"
+  "\377\356\354\352\377\350\346\345\377\331\327\325\377\316\313\312\377"
+  "\202\202\201\377t{\177\377u}\200\377t|\177\377r{\177\377\205\217\223"
+  "\377\236\252\256\377\234\250\255\377\234\247\253\376\236\237\237\344"
+  "\245\245\244\327\244\244\244\325\243\243\242\323LLLr\0\0\0\22\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\3\0\0\0\14\0\0\0\32\0\0\0:\24$*\241\320\324"
+  "\325\377\376\376\374\377\376\376\374\377\376\376\374\377\374\374\372"
+  "\377\252\252\251\377oon\377www\377\230\230\230\377\304\304\304\377\337"
+  "\337\337\377\344\344\344\377\333\333\333\377\310\310\310\377\310\310"
+  "\310\377\353\353\353\377\271\271\271\377\362\362\362\377\344\344\344"
+  "\377\264\264\264\377kkj\377\233\233\232\377\257\257\255\377\266\266\264"
+  "\377\352\351\347\377\362\360\356\377\311\310\307\377\263\262\260\377"
+  "\312\310\306\377\331\327\325\377\333\330\327\377\312\307\305\377\206"
+  "\204\203\377\233\233\232\377\266\266\264\377\266\266\264\377\273\273"
+  "\271\377\364\364\362\377\376\376\374\377\376\376\374\377\361\341\330"
+  "\377\362\335\323\377\362\337\325\377\363\341\331\377\374\372\367\377"
+  "kkk\246\0\0\0\34\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\1\0\0\0\7\0\0\0\23"
+  "\0\0\0'\0\0\0c\233\244\250\361\376\376\374\377\376\376\374\377\376\376"
+  "\374\377\376\376\374\377\374\374\372\377\322\322\322\377\225\225\224"
+  "\377vvu\377ssr\377\214\214\214\377\262\262\262\377\266\266\266\377\277"
+  "\277\277\377\361\361\361\377\345\345\345\377\266\266\266\377\311\311"
+  "\311\377\305\305\305\377jjj\377\231\231\230\377\252\252\250\377\263\263"
+  "\262\377\340\337\336\377\360\356\354\377\356\353\352\377\345\343\341"
+  "\377\265\262\261\377\244\242\240\377\242\240\237\377\241\236\236\377"
+  "\237\234\233\377\230\225\223\377vvu\377\266\266\264\377\274\274\272\377"
+  "\354\354\352\377\376\376\374\377\376\376\374\377\376\376\374\377\343"
+  "yj\377\340F9\377\3325$\377\315Q=\377\365\363\356\377fff\250\0\0\0\37"
+  "\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\4\0\0\0\14\0\0\0\35\0\0\0"
+  "Gmmm\267\375\375\373\377\376\376\374\377\376\376\374\377\376\376\374"
+  "\377\376\376\374\377\376\376\374\377\362\362\360\377\320\320\316\377"
+  "\245\245\243\377\216\216\216\377\177\177\177\377\234\234\234\377\352"
+  "\352\352\377\376\376\376\377\361\361\361\377\267\267\267\377\266\266"
+  "\266\377nnn\377\223\223\222\377\240\240\237\377\260\257\256\377\336\335"
+  "\333\377\356\354\352\377\354\352\350\377\350\346\344\377\346\344\342"
+  "\377\340\334\333\377\302\277\275\377\250\246\245\377\236\233\232\377"
+  "\233\230\227\377\233\230\226\377oon\377\327\327\325\377\367\367\365\377"
+  "\376\376\374\377\376\376\374\377\376\376\374\377\376\376\374\377\337"
+  "\236\227\377\312\277\272\377\343\240\231\377\325TD\377\365\362\352\377"
+  "ccc\250\0\0\0\40\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\2\0\0\0\10"
+  "\0\0\0\25\0\0\0""8@@@}\353\353\351\377\376\376\374\377\376\376\374\377"
+  "\376\376\374\377\376\376\374\377\376\376\374\377\376\376\374\377\376"
+  "\376\374\377\376\376\374\377\357\357\356\377\326\326\325\377\313\313"
+  "\312\377\241\241\240\377\245\245\245\377\342\342\342\377\340\340\340"
+  "\377}}|\377\215\215\214\377\240\237\236\377\274\272\271\377\334\333\332"
+  "\377\344\342\340\377\344\342\340\377\346\343\342\377\344\342\340\377"
+  "\342\337\335\377\340\334\332\377\334\331\330\377\331\325\323\377\322"
+  "\316\315\377\316\312\310\377\315\310\307\377\211\206\205\377\373\373"
+  "\371\377\376\376\374\377\376\376\374\377\376\376\374\377\376\376\374"
+  "\377\371\371\367\377\312\204x\377\322\310\313\377\323\313\313\377\343"
+  "ob\377\366\362\352\377```\250\0\0\0\40\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\1\0\0\0\5\0\0\0\17\0\0\0*\5\5\5V\316\316\315\366\376\376"
+  "\374\377\376\376\374\377\376\376\374\377\376\376\374\377\376\376\374"
+  "\377\376\376\374\377\376\376\374\377\376\376\374\377\376\376\374\377"
+  "\374\374\372\377\350\350\346\377\332\332\331\377\330\330\330\377\251"
+  "\251\250\377\220\220\220\377\265\265\263\377\326\325\324\377\340\336"
+  "\335\377\344\343\341\377\342\340\336\377\340\335\334\377\334\332\331"
+  "\377\332\330\326\377\333\330\326\377\334\330\326\377\334\327\326\377"
+  "\330\324\322\377\326\322\320\377\324\316\315\377\320\314\312\377\316"
+  "\310\307\377\233\227\226\377\332\332\330\377\376\376\374\377\376\376"
+  "\374\377\376\376\374\377\376\376\374\377\356\356\354\377\314pc\377\315"
+  "ZP\377\321{v\377\341RB\377\365\361\351\377\\\\\\\250\0\0\0\40\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\3\0\0\0\13\0\0\0\37\0\0\0"
+  "L\250\250\250\316\376\376\374\377\376\376\374\377\376\376\374\377\376"
+  "\376\374\377\376\376\374\377\376\376\374\377\376\376\374\377\376\376"
+  "\374\377\376\376\374\377\370\370\366\377\365\365\363\377\361\361\357"
+  "\377\346\345\344\377\337\336\334\377\336\336\334\377\336\335\334\377"
+  "\342\340\337\377\344\341\337\377\340\336\334\377\336\334\332\377\334"
+  "\331\330\377\331\326\324\377\326\323\322\377\324\320\317\377\322\316"
+  "\314\377\320\314\313\377\322\316\314\377\322\314\313\377\320\312\310"
+  "\377\314\307\306\377\312\304\302\377\277\271\270\377\237\237\236\377"
+  "\376\376\374\377\376\376\374\377\376\376\374\377\376\376\374\377\375"
+  "\375\373\377\306\233\220\377\317\202u\377\332\226\210\377\344\263\243"
+  "\377\362\357\353\377YXX\250\0\0\0\40\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\2\0\0\0\11\0\0\0\30\0\0\0Bppp\245\375\375\373\377"
+  "\376\376\374\377\376\376\374\377\376\376\374\377\376\376\374\377\376"
+  "\376\374\377\376\376\374\377\376\376\374\377\373\373\371\377\364\364"
+  "\362\377\362\361\357\377\356\356\354\377\354\353\351\377\350\347\345"
+  "\377\342\340\336\377\340\336\335\377\342\337\336\377\336\334\333\377"
+  "\334\332\330\377\332\330\326\377\330\324\323\377\326\322\320\377\322"
+  "\317\315\377\320\314\312\377\316\311\307\377\312\306\304\377\310\303"
+  "\302\377\307\302\301\377\310\303\302\377\310\302\300\377\306\277\276"
+  "\377\303\274\273\377\207\205\204\377\375\375\373\377\376\376\374\377"
+  "\376\376\374\377\376\375\373\377\375\374\372\377\366\365\364\377\371"
+  "\370\366\377\366\366\364\377\367\366\364\377\365\364\362\377UUS\250\0"
+  "\0\0\40\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\1\0\0\0\6\0"
+  "\0\0\23\0\0\0""8<<<z\350\350\346\377\376\376\374\377\376\376\374\377"
+  "\376\376\374\377\376\376\374\377\376\376\374\377\376\376\374\377\375"
+  "\375\373\377\363\363\361\377\360\357\355\377\355\354\352\377\352\351"
+  "\347\377\350\346\344\377\346\343\342\377\342\340\337\377\340\336\334"
+  "\377\336\333\332\377\333\330\327\377\330\326\324\377\326\323\321\377"
+  "\324\320\316\377\321\315\313\377\316\312\310\377\314\307\306\377\312"
+  "\304\302\377\306\301\300\377\304\277\276\377\302\274\273\377\277\272"
+  "\270\377\276\270\266\377\276\270\266\377\276\270\266\377xut\377\372\372"
+  "\370\377\374\374\372\377\373\373\371\377\371\371\367\377\370\367\365"
+  "\377\366\366\364\377\365\364\362\377\363\362\360\377\362\360\357\377"
+  "\360\357\355\377PPP\250\0\0\0\40\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\1\0\0\0\5\0\0\0\16\0\0\0*\6\6\6V\310\310\307\367\376\376"
+  "\374\377\376\376\374\377\376\376\374\377\376\376\374\377\376\376\374"
+  "\377\374\374\372\377\364\364\362\377\356\356\354\377\354\352\350\377"
+  "\350\350\346\377\346\344\343\377\344\342\340\377\341\337\335\377\336"
+  "\334\332\377\334\332\330\377\332\327\325\377\330\324\322\377\324\321"
+  "\320\377\322\316\314\377\320\313\312\377\314\310\306\377\312\306\304"
+  "\377\310\302\301\377\305\300\276\377\302\276\274\377\300\273\271\377"
+  "\276\270\266\377\274\264\263\377\271\262\260\377\241\233\231\377wsr\377"
+  "jhg\377\362\362\360\377\370\367\365\377\366\365\363\377\365\364\362\377"
+  "\363\362\360\377\362\360\356\377\360\357\355\377\357\355\353\377\355"
+  "\353\351\377\354\351\350\377LLL\250\0\0\0\40\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\3\0\0\0\13\0\0\0\40\0\0\0L\240\240"
+  "\237\321\376\376\374\377\376\376\374\377\376\376\374\377\375\375\373"
+  "\377\372\372\370\377\366\365\364\377\354\354\352\377\352\350\347\377"
+  "\350\346\344\377\344\343\341\377\342\340\336\377\340\335\334\377\334"
+  "\332\331\377\332\330\326\377\330\325\324\377\326\322\320\377\324\317"
+  "\316\377\320\314\312\377\316\312\310\377\314\306\305\377\310\304\302"
+  "\377\306\300\300\377\304\276\275\377\300\274\272\377\277\271\270\377"
+  "\247\241\240\377~zy\377vtt\377xwv\377\220\220\217\377\267\267\267\377"
+  "\271\270\266\377\345\343\342\377\363\362\360\377\362\360\356\377\360"
+  "\356\355\377\357\355\353\377\355\353\351\377\353\351\347\377\352\350"
+  "\346\377\350\346\344\377\347\344\342\377GGG\250\0\0\0\40\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\2\0\0\0\11\0\0\0\30\0"
+  "\0\0Cfff\247\376\376\374\377\376\376\374\377\374\374\372\377\372\371"
+  "\367\377\366\366\364\377\354\353\352\377\350\347\345\377\346\344\342"
+  "\377\344\341\337\377\340\336\334\377\336\334\332\377\334\331\330\377"
+  "\331\326\324\377\326\323\322\377\324\320\317\377\322\316\314\377\317"
+  "\313\311\377\314\310\306\377\312\304\303\377\310\302\300\377\304\277"
+  "\276\377\255\250\247\377\204\200\177\377ron\377_^]\377eed\377\214\213"
+  "\213\377\232\231\230\377\247\246\245\377\274\273\271\377\302\302\300"
+  "\377\350\346\344\377\360\356\355\377\356\355\353\377\355\353\351\377"
+  "\353\351\347\377\352\347\346\377\350\346\344\377\347\344\342\377\345"
+  "\342\341\377\344\341\337\377\342\337\335\377DCC\250\0\0\0\40\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\1\0\0\0\6\0\0\0\23"
+  "\0\0\0""8555}\345\345\343\377\372\372\370\377\370\367\365\377\365\364"
+  "\362\377\356\355\353\377\346\345\343\377\344\342\340\377\342\337\336"
+  "\377\336\334\333\377\334\332\330\377\332\330\326\377\330\324\323\377"
+  "\326\322\320\377\322\317\315\377\320\314\312\377\316\311\307\377\312"
+  "\306\304\377\263\257\255\377\213\210\207\377\177~}\377\202\201\200\377"
+  "\230\230\230\377\277\277\275\377\313\313\311\377\302\301\300\377\272"
+  "\271\270\377\300\277\275\377\320\317\315\377\332\331\327\377\333\332"
+  "\330\377\353\351\347\377\355\353\351\377\353\351\347\377\352\347\345"
+  "\377\350\346\344\377\347\344\342\377\345\342\341\377\344\340\337\377"
+  "\342\337\335\377\340\335\333\377\337\333\332\377\335\332\330\377@\77"
+  "\77\250\0\0\0\40\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\1\0\0\0\5\0\0\0\16\0\0\0+\7\7\7W\275\275\274\370\366\365\364\377"
+  "\364\362\360\377\357\356\354\377\346\344\342\377\342\340\337\377\340"
+  "\336\334\377\336\333\332\377\333\330\327\377\330\326\324\377\326\323"
+  "\321\377\324\320\316\377\322\315\314\377\271\266\264\377\224\221\220"
+  "\377\212\210\207\377\205\204\203\377\220\220\217\377\233\233\231\377"
+  "\225\225\225\377\230\230\226\377\251\250\247\377\302\301\277\377\327"
+  "\325\324\377\335\335\333\377\326\325\323\377\316\314\312\377\312\311"
+  "\310\377\310\306\304\377\334\332\330\377\352\347\345\377\350\345\344"
+  "\377\347\344\342\377\345\342\340\377\343\340\337\377\342\337\335\377"
+  "\340\335\333\377\337\333\331\377\335\332\330\377\334\330\326\377\332"
+  "\326\325\377\331\324\323\377<;;\250\0\0\0\40\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\3\0\0\0\13\0\0\0\40\0\0\0"
+  "M\223\223\223\323\362\360\356\377\357\356\354\377\346\344\342\377\342"
+  "\337\335\377\336\334\332\377\334\332\330\377\332\327\325\377\330\324"
+  "\322\377\277\274\273\377\231\227\226\376\221\217\216\377\215\214\213"
+  "\377\240\240\237\377\303\303\301\377\316\315\314\377\325\325\324\377"
+  "\337\336\334\377\350\347\346\377\357\356\354\377\361\360\356\377\362"
+  "\360\356\377\361\357\356\377\357\356\354\377\356\354\352\377\354\352"
+  "\351\377\353\351\347\377\351\347\345\377\350\345\343\377\346\344\342"
+  "\377\345\342\340\377\343\340\337\377\342\336\335\377\340\335\333\377"
+  "\337\333\331\377\335\331\330\377\333\330\326\377\332\326\324\377\330"
+  "\324\323\377\327\323\321\377\325\321\317\377\324\317\315\377866\250\0"
+  "\0\0\40\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\2\0\0\0\11\0\0\0\31\0\0\0DYYY\253\356\354\352\377\350\345\344\377"
+  "\340\335\334\377\334\332\331\377\305\303\302\377\237\235\235\372zyy\327"
+  "HGG\254\35\35\35\213www\337\317\317\315\377\327\326\324\377\340\340\336"
+  "\377\352\351\347\377\360\357\355\377\362\361\360\377\363\362\360\377"
+  "\362\361\357\377\361\357\355\377\357\356\354\377\356\354\352\377\354"
+  "\352\351\377\353\351\347\377\351\347\345\377\350\345\343\377\346\343"
+  "\342\377\345\342\340\377\343\340\336\377\341\336\335\377\340\335\333"
+  "\377\336\333\331\377\335\331\327\377\333\330\326\377\332\326\324\377"
+  "\330\324\323\377\327\322\321\377\325\321\317\377\324\317\315\377\322"
+  "\315\314\377\321\313\312\377\317\312\310\377322\250\0\0\0\40\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\1\0\0\0\6"
+  "\0\0\0\23\0\0\0""8+++~\274\272\271\377\247\245\244\372\202\200\200\326"
+  "NNN\254%%%\201\6\6\6X\0\0\0M\0\0\0F\0\0\0K\222\222\221\330\361\361\357"
+  "\377\364\363\361\377\364\363\362\377\364\363\361\377\362\361\357\377"
+  "\361\357\355\377\357\355\354\377\356\354\352\377\354\352\350\377\353"
+  "\350\347\377\351\347\345\377\350\345\343\377\346\343\341\377\345\342"
+  "\340\377\343\340\336\377\341\336\335\377\340\334\333\377\336\333\331"
+  "\377\335\331\327\377\333\327\326\377\332\326\324\377\330\324\322\377"
+  "\327\322\321\377\325\321\317\377\324\317\315\377\322\315\313\377\321"
+  "\313\312\377\317\312\310\377\316\310\307\377\314\306\305\377\312\305"
+  "\303\377/..\250\0\0\0\40\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\1\0\0\0\5\0\0\0\16\0\0\0)\0\0\0P\"\"\"v\7\7\7"
+  "X\0\0\0M\0\0\0D\0\0\0""9\0\0\0-\0\0\0$\0\0\0#\0\0\0""5\227\227\226\326"
+  "\364\363\361\377\362\361\357\377\361\357\355\377\357\355\354\377\356"
+  "\354\352\377\354\352\350\377\353\350\347\377\351\347\345\377\347\345"
+  "\343\377\346\343\341\377\344\341\340\377\343\340\336\377\341\336\334"
+  "\377\340\334\333\377\336\333\331\377\335\331\327\377\333\327\325\377"
+  "\332\326\324\377\330\324\322\377\327\322\321\377\325\320\317\377\323"
+  "\317\315\377\322\315\313\377\320\313\312\377\317\312\310\377\315\310"
+  "\306\377\314\306\305\377\312\305\303\377\311\303\301\377\307\301\277"
+  "\377\306\277\276\377+**\250\0\0\0\40\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\3\0\0\0\12\0\0\0\30\0\0\0"
+  "0\0\0\0""5\0\0\0,\0\0\0\"\0\0\0\34\0\0\0\26\0\0\0\22\0\0\0\21\0\0\0\27"
+  "\0\0\0""1\223\222\222\326\357\355\353\377\355\353\352\377\354\352\350"
+  "\377\352\350\346\377\351\347\345\377\347\345\343\377\346\343\341\377"
+  "\344\341\340\377\343\340\336\377\341\336\334\377\340\334\333\377\336"
+  "\332\331\377\335\331\327\377\333\327\325\377\331\325\324\377\330\324"
+  "\322\377\326\322\320\377\325\320\317\377\323\317\315\377\322\315\313"
+  "\377\320\313\311\377\317\311\310\377\315\310\306\377\314\306\305\377"
+  "\312\304\303\377\311\303\301\377\307\301\277\377\306\277\276\377\304"
+  "\276\274\377\302\274\272\377\301\272\271\377'&&\250\0\0\0\40\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\1"
+  "\0\0\0\5\0\0\0\14\0\0\0\21\0\0\0\23\0\0\0\21\0\0\0\16\0\0\0\13\0\0\0"
+  "\10\0\0\0\6\0\0\0\10\0\0\0\22\0\0\0/\216\215\214\325\352\350\346\377"
+  "\351\346\345\377\347\345\343\377\346\343\341\377\344\341\337\377\342"
+  "\337\336\377\341\336\334\377\337\334\332\377\336\332\331\377\334\331"
+  "\327\377\333\327\325\377\331\325\324\377\330\324\322\377\326\322\320"
+  "\377\325\320\317\377\323\316\315\377\322\315\313\377\320\313\311\377"
+  "\317\311\310\377\315\310\306\377\314\306\304\377\312\304\303\377\311"
+  "\303\301\377\307\301\277\377\306\277\276\377\304\275\274\377\302\274"
+  "\272\377\301\272\271\377\277\270\267\377\276\267\265\377\274\265\263"
+  "\377!\40\40\250\0\0\0\40\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\2\0\0\0\4\0\0\0\6\0\0\0\7\0\0"
+  "\0\5\0\0\0\4\0\0\0\3\0\0\0\2\0\0\0\2\0\0\0\5\0\0\0\17\0\0\0'777|VUU\252"
+  "TTT\252RRR\252QPP\252ONN\252MLL\252LKK\252III\252HHG\252FEE\252DCC\252"
+  "CBB\252@@@\252\77>=\252=<<\252<::\252999\252776\252654\252433\252311"
+  "\252100\252.--\252-,+\252+**\252*((\252(''\252%%$\252$#\"\252\"!!\252"
+  "\37\36\36\251\1\1\1_\0\0\0\34\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\1\0\0\0\1\0\0\0\1"
+  "\0\0\0\1\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\0\0\0\14\0\0\0\32\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\23\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\1\0\0\0\7\0\0\0"
+  "\17\0\0\0\26\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0"
+  "\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30"
+  "\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0"
+  "\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0\0\0\30\0"
+  "\0\0\30\0\0\0\27\0\0\0\22\0\0\0\13\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\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\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\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\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+  "\0\0\0\0\0\0\0\0\0"};
+
+
diff --git a/src/plugins/notification/version.rc b/src/plugins/notification/version.rc
new file mode 100644 (file)
index 0000000..1533614
--- /dev/null
@@ -0,0 +1,36 @@
+1 VERSIONINFO
+ FILEVERSION 0, 0, 0, 0
+ PRODUCTVERSION 0, 0, 0, 0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "000004b0"
+        BEGIN
+            VALUE "FileDescription", "notification Plugin\0"
+            VALUE "FileVersion", "0.0.0.0\0"
+            VALUE "ProductVersion", "0.0.0.0 Win32\0"
+            VALUE "LegalCopyright", "GPL / Â© 1999-2008 Hiroyuki Yamamoto & The Claws Mail Team\0"
+            VALUE "CompanyName", "GNU / Free Software Foundation\0"
+            VALUE "ProductName", "Claws Mail\0"
+//            VALUE "Comments", "\0"
+//            VALUE "InternalName", "\0"
+//            VALUE "LegalTrademarks", "\0"
+//            VALUE "OriginalFilename", "\0"
+//            VALUE "PrivateBuild", "\0"
+//            VALUE "SpecialBuild", "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x0, 1200
+    END
+END
diff --git a/src/plugins/pdf_viewer/Makefile.am b/src/plugins/pdf_viewer/Makefile.am
new file mode 100644 (file)
index 0000000..56fdeaa
--- /dev/null
@@ -0,0 +1,26 @@
+plugindir = $(pkglibdir)/plugins
+
+plugin_LTLIBRARIES = pdf_viewer.la
+
+pdf_viewer_la_SOURCES = \
+         poppler_viewer.c \
+         poppler_viewer.h \
+        gettext.h
+
+pdf_viewer_la_LDFLAGS = \
+       -avoid-version -module \
+       $(GTK_LIBS) \
+       $(POPPLER_LIBS)
+
+INCLUDES = \
+       -I$(top_srcdir)/src \
+       -I$(top_srcdir)/src/common \
+       -I$(top_srcdir)/src/gtk
+
+AM_CPPFLAGS = \
+       -Wall \
+       $(CLAWS_MAIL_CFLAGS) \
+       $(GLIB_CFLAGS) \
+       $(GTK_CFLAGS) \
+       $(POPPLER_CFLAGS) \
+       -DLOCALEDIR=\""$(localedir)"\"
diff --git a/src/plugins/pdf_viewer/doc_index.xpm b/src/plugins/pdf_viewer/doc_index.xpm
new file mode 100644 (file)
index 0000000..8ae3eda
--- /dev/null
@@ -0,0 +1,21 @@
+/* XPM */
+static char * doc_index_xpm[] = {
+"14 14 4 1",
+"      c None",
+".     c #1F0000",
+"+     c #4D0202",
+"@     c #000000",
+"........      ",
+".++++++.      ",
+".+@@@@@@      ",
+".+@    @      ",
+".+@    @      ",
+".+@    @      ",
+"...@@@@@      ",
+"    @@     @@@",
+"    @@@@@@@@@@",
+"    @@     @@@",
+"    @@        ",
+"    @@     @@@",
+"    @@@@@@@@@@",
+"           @@@"};
diff --git a/src/plugins/pdf_viewer/doc_index_close.xpm b/src/plugins/pdf_viewer/doc_index_close.xpm
new file mode 100644 (file)
index 0000000..497dd1d
--- /dev/null
@@ -0,0 +1,19 @@
+/* XPM */
+static char * doc_index_close_xpm[] = {
+"14 14 2 1",
+"      c None",
+".     c #000000",
+"              ",
+"              ",
+"  ...   ...   ",
+"  .... ....   ",
+"   ... ...    ",
+"    .....     ",
+"    .....     ",
+"     ...      ",
+"    .....     ",
+"    .....     ",
+"   ... ...    ",
+"  ...   ...   ",
+"  ...   ...   ",
+"              "};
diff --git a/src/plugins/pdf_viewer/doc_info.xpm b/src/plugins/pdf_viewer/doc_info.xpm
new file mode 100644 (file)
index 0000000..c1097cb
--- /dev/null
@@ -0,0 +1,104 @@
+/* XPM */
+static char * doc_info_xpm[] = {
+"14 14 87 1",
+"      c None",
+".     c #000000",
+"+     c #030303",
+"@     c #4F4F4F",
+"#     c #ABABAB",
+"$     c #D3D3D3",
+"%     c #ADADAD",
+"&     c #515151",
+"*     c #040404",
+"=     c #121212",
+"-     c #B1B1B1",
+";     c #EFEFEF",
+">     c #8F8F8F",
+",     c #4C4C4C",
+"'     c #939393",
+")     c #E7E7E7",
+"!     c #B4B4B4",
+"~     c #141414",
+"{     c #D4D4D4",
+"]     c #373737",
+"^     c #8E8E8E",
+"/     c #929292",
+"(     c #0C0C0C",
+"_     c #323232",
+":     c #CECECE",
+"<     c #B6B6B6",
+"[     c #505050",
+"}     c #EBEBEB",
+"|     c #424242",
+"1     c #010101",
+"2     c #737373",
+"3     c #747474",
+"4     c #090909",
+"5     c #0B0B0B",
+"6     c #484848",
+"7     c #F6F6F6",
+"8     c #555555",
+"9     c #919191",
+"0     c #0A0A0A",
+"a     c #060606",
+"b     c #2A2A2A",
+"c     c #252525",
+"d     c #0E0E0E",
+"e     c #161616",
+"f     c #B3B3B3",
+"g     c #D5D5D5",
+"h     c #050505",
+"i     c #ACACAC",
+"j     c #E0E0E0",
+"k     c #080808",
+"l     c #7B7B7B",
+"m     c #DADADA",
+"n     c #D6D6D6",
+"o     c #020202",
+"p     c #979797",
+"q     c #070707",
+"r     c #6A6A6A",
+"s     c #DBDBDB",
+"t     c #B0B0B0",
+"u     c #898989",
+"v     c #969696",
+"w     c #DEDEDE",
+"x     c #0F0F0F",
+"y     c #B5B5B5",
+"z     c #1B1B1B",
+"A     c #111111",
+"B     c #191919",
+"C     c #E4E4E4",
+"D     c #5A5A5A",
+"E     c #B9B9B9",
+"F     c #C0C0C0",
+"G     c #262626",
+"H     c #181818",
+"I     c #BEBEBE",
+"J     c #BDBDBD",
+"K     c #BABABA",
+"L     c #E8E8E8",
+"M     c #A5A5A5",
+"N     c #4B4B4B",
+"O     c #828282",
+"P     c #E3E3E3",
+"Q     c #171717",
+"R     c #595959",
+"S     c #DCDCDC",
+"T     c #DDDDDD",
+"U     c #B7B7B7",
+"V     c #5B5B5B",
+"     .++.     ",
+"   +@#$$%&*   ",
+"  =-;>,,')!~  ",
+" +-{].^/(_:<* ",
+" [}|1.2345678.",
+".%90.abcade<f.",
+"*g8h*4ij+kdlma",
+"*n80*opj1qdrsa",
+".tuah.vw15xuy.",
+".8)zaA%$xoBCD.",
+" *EFGao..HIJh ",
+"  eKLMNNOPJQ  ",
+"   hR<STUVa   ",
+"    ..qq..    "};
diff --git a/src/plugins/pdf_viewer/first_arrow.xpm b/src/plugins/pdf_viewer/first_arrow.xpm
new file mode 100644 (file)
index 0000000..129c157
--- /dev/null
@@ -0,0 +1,49 @@
+/* XPM */
+static char * first_arrow_xpm[] = {
+"16 16 30 1",
+"      c None",
+".     c #1D2F3F",
+"+     c #1D2F40",
+"@     c #1C2C3C",
+"#     c #3F6588",
+"$     c #000000",
+"%     c #1C2E3E",
+"&     c #5B88B2",
+"*     c #5080AD",
+"=     c #9EB8D1",
+"-     c #192937",
+";     c #080D11",
+">     c #9CB7D1",
+",     c #A4BDD5",
+"'     c #B6CADD",
+")     c #B2C7DB",
+"!     c #AFC5DA",
+"~     c #B5C9DC",
+"{     c #5F8BB4",
+"]     c #97B3CE",
+"^     c #A0BAD3",
+"/     c #9AB5CF",
+"(     c #9BB6D0",
+"_     c #4C79A3",
+":     c #95B2CE",
+"<     c #91B0CC",
+"[     c #2A435B",
+"}     c #49749C",
+"|     c #375978",
+"1     c #456F96",
+"                ",
+".+              ",
+"@#     $        ",
+"%#    $$        ",
+"@#   $&$        ",
+"@#  $*=$$$$$$$  ",
+"-# ;*>,''')!~$  ",
+"%#${]>^/(>>^($  ",
+"@$_:^>>>(//=<$  ",
+"@#$[########}$  ",
+"@# $[##|||||1$  ",
+"@#  $[#$$$$$$$  ",
+"@#   $[$        ",
+"%#    $$        ",
+"@%     $        ",
+"                "};
diff --git a/src/plugins/pdf_viewer/last_arrow.xpm b/src/plugins/pdf_viewer/last_arrow.xpm
new file mode 100644 (file)
index 0000000..c04a559
--- /dev/null
@@ -0,0 +1,49 @@
+/* XPM */
+static char * last_arrow_xpm[] = {
+"16 16 30 1",
+"      c None",
+".     c #1D2F40",
+"+     c #1D2F3F",
+"@     c #000000",
+"#     c #3F6588",
+"$     c #1C2C3C",
+"%     c #1C2E3E",
+"&     c #5B88B2",
+"*     c #9EB8D1",
+"=     c #5080AD",
+"-     c #B5C9DC",
+";     c #AFC5DA",
+">     c #B2C7DB",
+",     c #B6CADD",
+"'     c #A4BDD5",
+")     c #9CB7D1",
+"!     c #080D11",
+"~     c #192937",
+"{     c #9BB6D0",
+"]     c #A0BAD3",
+"^     c #9AB5CF",
+"/     c #97B3CE",
+"(     c #5F8BB4",
+"_     c #91B0CC",
+":     c #95B2CE",
+"<     c #4C79A3",
+"[     c #49749C",
+"}     c #2A435B",
+"|     c #456F96",
+"1     c #375978",
+"                ",
+"              .+",
+"        @     #$",
+"        @@    #%",
+"        @&@   #$",
+"  @@@@@@@*=@  #$",
+"  @-;>,,,')=! #~",
+"  @{])){^])/(@#%",
+"  @_*^^{)))]:<@$",
+"  @[########}@#$",
+"  @|11111##}@ #$",
+"  @@@@@@@#}@  #$",
+"        @}@   #$",
+"        @@    #%",
+"        @     %$",
+"                "};
diff --git a/src/plugins/pdf_viewer/left_arrow.xpm b/src/plugins/pdf_viewer/left_arrow.xpm
new file mode 100644 (file)
index 0000000..d6dd97c
--- /dev/null
@@ -0,0 +1,44 @@
+/* XPM */
+static char * left_arrow_xpm[] = {
+"16 16 25 1",
+"      c None",
+".     c #000000",
+"+     c #5B88B2",
+"@     c #5080AD",
+"#     c #9EB8D1",
+"$     c #080D11",
+"%     c #9CB7D1",
+"&     c #A4BDD5",
+"*     c #B6CADD",
+"=     c #B2C7DB",
+"-     c #AFC5DA",
+";     c #B5C9DC",
+">     c #5F8BB4",
+",     c #97B3CE",
+"'     c #A0BAD3",
+")     c #9AB5CF",
+"!     c #9BB6D0",
+"~     c #4C79A3",
+"{     c #95B2CE",
+"]     c #91B0CC",
+"^     c #2A435B",
+"/     c #3F6588",
+"(     c #49749C",
+"_     c #375978",
+":     c #456F96",
+"                ",
+"                ",
+"       .        ",
+"      ..        ",
+"     .+.        ",
+"    .@#.......  ",
+"   $@%&***=-;.  ",
+"  .>,%')!%%'!.  ",
+" .~{'%%%!))#].  ",
+"  .^////////(.  ",
+"   .^//_____:.  ",
+"    .^/.......  ",
+"     .^.        ",
+"      ..        ",
+"       .        ",
+"                "};
diff --git a/src/plugins/pdf_viewer/placeholder.txt b/src/plugins/pdf_viewer/placeholder.txt
deleted file mode 100644 (file)
index 3b94f91..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Placeholder
diff --git a/src/plugins/pdf_viewer/poppler_viewer.c b/src/plugins/pdf_viewer/poppler_viewer.c
new file mode 100644 (file)
index 0000000..60f8909
--- /dev/null
@@ -0,0 +1,2106 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright(C) 1999-2007 the Claws Mail Team
+ * This file Copyright (C) 2007 Salvatore De Paolis 
+ * <iwkse@claws-mail.org> 
+ *
+ * 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "poppler_viewer.h"
+#include "printing.h"
+#include "prefs_common.h"
+#include "gtk/gtkutils.h"
+#include "mimeview.h"
+#ifndef POPPLER_WITH_GDK
+#include "stdbool.h"
+#endif
+
+static FileType pdf_viewer_mimepart_get_type(MimeInfo *partinfo);
+static MimeViewerFactory pdf_viewer_factory;
+
+static void pdf_viewer_show_mimepart(MimeViewer *_viewer, const gchar *infile,
+                               MimeInfo *partinfo);
+
+static MimeViewer *pdf_viewer_create(void);
+static void pdf_viewer_clear(MimeViewer *_viewer);
+static void pdf_viewer_destroy(MimeViewer *_viewer);
+static void pdf_viewer_update(MimeViewer *_viewer, gboolean reload_file, int page_num);
+
+static GtkWidget *pdf_viewer_get_widget(MimeViewer *_viewer);
+
+static void pdf_viewer_hide_index_pane(PdfViewer *viewer);
+static void pdf_viewer_set_index_button_sensitive(PdfViewer *viewer);
+static void pdf_viewer_scroll_to(PdfViewer *viewer, gfloat x, gfloat y);
+
+static void search_matches_free(PdfViewer *viewer);
+static gboolean        pdf_viewer_text_search(MimeViewer *_viewer, gboolean backward,
+                                    const gchar *str, gboolean case_sens);
+static void pdf_viewer_render_selection(PdfViewer *viewer, PopplerRectangle *rect, PageResult *page_results);
+static void pdf_viewer_render_page(PopplerPage *page, GtkWidget *view, double width, double height, double zoom, gint rotate);
+
+static char * pdf_viewer_get_document_format_data(GTime utime);
+static void pdf_viewer_get_document_index(PdfViewer *viewer, PopplerIndexIter *index_iter, GtkTreeIter *parentiter);
+static void pdf_viewer_index_row_activated(GtkTreeView         *tree_view,
+                                       GtkTreePath             *path,
+                                       GtkTreeViewColumn       *column,
+                                       gpointer                 data);
+
+static GtkTable * pdf_viewer_fill_info_table(PdfViewer *viewer);
+
+/* Callbacks */
+static void pdf_viewer_move_events_cb(GtkWidget *widget, GdkEventMotion *event, PdfViewer *viewer); 
+static void pdf_viewer_button_press_events_cb(GtkWidget *widget, GdkEventButton *event, PdfViewer *viewer); 
+static void pdf_viewer_mouse_scroll_destroy_cb(GtkWidget *widget, GdkEventButton *event, PdfViewer *viewer); 
+static void pdf_viewer_button_first_page_cb(GtkButton *button, PdfViewer *viewer);
+static void pdf_viewer_button_last_page_cb(GtkButton *button, PdfViewer *viewer);
+static void pdf_viewer_button_zoom_in_cb(GtkButton *button, PdfViewer *viewer);
+static void pdf_viewer_button_zoom_out_cb(GtkButton *button, PdfViewer *viewer);
+static void pdf_viewer_button_zoom_fit_cb(GtkButton *button, PdfViewer *viewer);
+static void pdf_viewer_button_zoom_width_cb(GtkButton *button, PdfViewer *viewer);
+static void pdf_viewer_button_rotate_right_cb(GtkButton *button, PdfViewer *viewer);
+static void pdf_viewer_button_rotate_left_cb(GtkButton *button, PdfViewer *viewer);
+static void pdf_viewer_spin_change_page_cb(GtkSpinButton *button, PdfViewer *viewer);
+static void pdf_viewer_spin_zoom_scroll_cb(GtkSpinButton *button, PdfViewer *viewer);
+/* Show/Hide the index pane */
+static void pdf_viewer_show_document_index_cb(GtkButton *button, PdfViewer *viewer);
+static void pdf_viewer_button_document_info_cb(GtkButton *button, PdfViewer *viewer);
+
+static void pdf_viewer_show_controls(PdfViewer *viewer, gboolean show);
+static gboolean pdf_viewer_scroll_page(MimeViewer *_viewer, gboolean up);
+static void pdf_viewer_scroll_one_line(MimeViewer *_viewer, gboolean up);
+
+/** Claws-Mail Plugin functions*/
+gint plugin_init(gchar **error);
+const gchar *plugin_name(void);
+const gchar *plugin_desc(void);
+const gchar *plugin_type(void);
+const gchar *plugin_licence(void);
+const gchar *plugin_version(void);
+struct PluginFeature *plugin_provides(void);
+
+#ifndef POPPLER_WITH_GDK
+static void
+copy_cairo_surface_to_pixbuf (cairo_surface_t *surface,
+                             GdkPixbuf       *pixbuf)
+{
+       int cairo_width, cairo_height, cairo_rowstride;
+       unsigned char *pixbuf_data, *dst, *cairo_data;
+       int pixbuf_rowstride, pixbuf_n_channels;
+       unsigned int *src;
+       int x, y;
+
+       cairo_width = cairo_image_surface_get_width (surface);
+       cairo_height = cairo_image_surface_get_height (surface);
+       cairo_rowstride = cairo_image_surface_get_stride (surface);
+       cairo_data = cairo_image_surface_get_data (surface);
+
+       pixbuf_data = gdk_pixbuf_get_pixels (pixbuf);
+       pixbuf_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+       pixbuf_n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+
+       if (cairo_width > gdk_pixbuf_get_width (pixbuf))
+               cairo_width = gdk_pixbuf_get_width (pixbuf);
+       if (cairo_height > gdk_pixbuf_get_height (pixbuf))
+               cairo_height = gdk_pixbuf_get_height (pixbuf);
+       for (y = 0; y < cairo_height; y++) {
+               src = (unsigned int *) (cairo_data + y * cairo_rowstride);
+               dst = pixbuf_data + y * pixbuf_rowstride;
+               for (x = 0; x < cairo_width; x++) {
+                       dst[0] = (*src >> 16) & 0xff;
+                       dst[1] = (*src >> 8) & 0xff; 
+                       dst[2] = (*src >> 0) & 0xff;
+                       if (pixbuf_n_channels == 4)
+                               dst[3] = (*src >> 24) & 0xff;
+                       dst += pixbuf_n_channels;
+                       src++;
+               }
+       }
+}
+static void
+_poppler_page_render_to_pixbuf (PopplerPage *page,
+                               int src_x, int src_y,
+                               int src_width, int src_height,
+                               double scale,
+                               int rotation,
+                               gboolean printing,
+                               GdkPixbuf *pixbuf)
+{
+       cairo_t *cr;
+       cairo_surface_t *surface;
+
+       surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+                                       src_width, src_height);
+       cr = cairo_create (surface);
+       cairo_save (cr);
+       switch (rotation) {
+       case 90:
+               cairo_translate (cr, src_x + src_width, -src_y);
+               break;
+       case 180:
+               cairo_translate (cr, src_x + src_width, src_y + src_height);
+               break;
+       case 270:
+               cairo_translate (cr, -src_x, src_y + src_height);
+               break;
+       default:
+               cairo_translate (cr, -src_x, -src_y);
+       }
+
+       if (scale != 1.0)
+               cairo_scale (cr, scale, scale);
+
+       if (rotation != 0)
+               cairo_rotate (cr, rotation * G_PI / 180.0);
+
+       if (printing)
+               poppler_page_render_for_printing (page, cr);
+       else
+               poppler_page_render (page, cr);
+       cairo_restore (cr);
+
+       cairo_set_operator (cr, CAIRO_OPERATOR_DEST_OVER);
+       cairo_set_source_rgb (cr, 1., 1., 1.);
+       cairo_paint (cr);
+
+       cairo_destroy (cr);
+
+       copy_cairo_surface_to_pixbuf (surface, pixbuf);
+       cairo_surface_destroy (surface);
+}
+
+/**
+ * poppler_page_render_to_pixbuf:
+ * @page: the page to render from
+ * @src_x: x coordinate of upper left corner  
+ * @src_y: y coordinate of upper left corner  
+ * @src_width: width of rectangle to render  
+ * @src_height: height of rectangle to render
+ * @scale: scale specified as pixels per point
+ * @rotation: rotate the document by the specified degree
+ * @pixbuf: pixbuf to render into
+ *
+ * First scale the document to match the specified pixels per point,
+ * then render the rectangle given by the upper left corner at
+ * (src_x, src_y) and src_width and src_height.
+ * This function is for rendering a page that will be displayed.
+ * If you want to render a page that will be printed use
+ * poppler_page_render_to_pixbuf_for_printing() instead
+ *
+ * Deprecated: 0.16
+ **/
+static void
+poppler_page_render_to_pixbuf (PopplerPage *page,
+                              int src_x, int src_y,
+                              int src_width, int src_height,
+                              double scale,
+                              int rotation,
+                              GdkPixbuf *pixbuf)
+{
+       g_return_if_fail (POPPLER_IS_PAGE (page));
+       g_return_if_fail (scale > 0.0);
+       g_return_if_fail (pixbuf != NULL);
+
+       _poppler_page_render_to_pixbuf (page, src_x, src_y,
+                                 src_width, src_height,
+                                 scale, rotation,
+                                 FALSE,
+                                 pixbuf);
+}
+#endif
+static GtkWidget *pdf_viewer_get_widget(MimeViewer *_viewer)
+{
+       PdfViewer *viewer = (PdfViewer *) _viewer;
+       debug_print("pdf_viewer_get_widget: %p\n", viewer->vbox);
+
+       return GTK_WIDGET(viewer->vbox);
+}
+/** Hide the index panel */
+static void pdf_viewer_hide_index_pane(PdfViewer *viewer)
+{
+       if (viewer->pdf_index) {   
+               poppler_index_iter_free(viewer->pdf_index);
+               viewer->pdf_index = NULL;
+               gtk_widget_hide(GTK_WIDGET(viewer->frame_index));
+       }
+}
+
+static void search_matches_free(PdfViewer *viewer)
+{
+       GList *cur; 
+       for(cur = viewer->text_found; cur; cur = cur->next) {
+               PageResult *res = (PageResult *)cur->data;
+               g_list_free(res->results);
+               g_free(res);
+       }
+       g_list_free(viewer->text_found);
+       viewer->text_found = NULL;
+       g_free(viewer->last_search);
+       viewer->last_search = NULL;
+       if (viewer->last_rect && viewer->last_page_result) {
+               viewer->last_rect = NULL;
+               viewer->last_page_result = NULL;
+               pdf_viewer_update((MimeViewer *)viewer, 
+                       FALSE, 
+                       gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(viewer->cur_page)));
+       }
+}
+
+static void pdf_viewer_scroll_to(PdfViewer *viewer, gfloat x, gfloat y)
+{
+       GtkAdjustment *vadj;
+       GtkAdjustment *hadj;
+       vadj = gtk_scrolled_window_get_vadjustment(
+               GTK_SCROLLED_WINDOW(viewer->scrollwin));
+       
+       if (y < vadj->value) {
+               vadj->value = y;
+       }
+       else {
+               while(y > vadj->value + vadj->page_size) {
+                       vadj->value += vadj->page_size;
+               }
+       }
+       
+       hadj = gtk_scrolled_window_get_hadjustment(
+               GTK_SCROLLED_WINDOW(viewer->scrollwin));
+       
+       if (x < hadj->value) {
+               hadj->value = x;
+       }
+       else {
+               while(x > hadj->value + hadj->page_size) {
+                       hadj->value += hadj->page_size;
+               }
+       }
+
+       g_signal_emit_by_name(G_OBJECT(hadj), "value-changed", 0);      
+       g_signal_emit_by_name(G_OBJECT(vadj), "value-changed", 0);      
+}
+static void pdf_viewer_render_page(PopplerPage *page, GtkWidget *view, double width, 
+                                                                                               double height, double zoom, gint rotate)
+{
+       GdkPixbuf *pb;
+       
+       debug_print("width: %f\n", width);
+       pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, 
+                               FALSE, 8, 
+                               (int)(width * zoom), 
+                               (int)(height * zoom));  
+       
+                       poppler_page_render_to_pixbuf(page, 0, 0, 
+                               (int)(width * zoom), 
+                               (int)(height * zoom), 
+                               zoom, rotate, pb);
+               
+                       gtk_image_set_from_pixbuf(GTK_IMAGE(view), pb);
+                       g_object_unref(G_OBJECT(pb));
+}
+static void pdf_viewer_render_selection(PdfViewer *viewer, PopplerRectangle *rect, PageResult *page_results)
+{
+       gint selw, selh;
+       double width_points, height_points;
+       gint width, height;
+       GdkPixbuf *sel_pb, *page_pb;
+       gfloat x1, x2, y1, y2;  
+       
+
+       gint cur_page_num = 
+               gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(viewer->cur_page));
+       
+       viewer->last_match = viewer->res_cnt;
+       
+       viewer->last_rect = NULL;
+       viewer->last_page_result = NULL;
+       if (cur_page_num != page_results->page_num) {
+               /* we changed page. update the view */
+               gtk_spin_button_set_value(GTK_SPIN_BUTTON(viewer->cur_page), 
+       (gdouble) page_results->page_num);
+       }
+                       
+       viewer->last_rect = rect;
+       viewer->last_page_result = page_results;
+
+       GTK_EVENTS_FLUSH();
+                               
+       poppler_page_get_size(POPPLER_PAGE(viewer->pdf_page), &width_points, &height_points);
+       width = (int)((width_points * viewer->zoom) + 0.5);
+       height = (int)((height_points * viewer->zoom) + 0.5);
+
+       if (viewer->rotate == 90) {
+               x1 = MIN(rect->y1,rect->y2) * viewer->zoom;
+               x2 = MAX(rect->y1,rect->y2) * viewer->zoom;
+               y1 = MAX(rect->x1,rect->x2) * viewer->zoom;
+               y2 = MIN(rect->x1,rect->x2) * viewer->zoom;
+               selw = (x2 - x1);
+               selh = (y1 - y2);
+
+       } else if (viewer->rotate == 180) {
+               x1 = width - rect->x2 * viewer->zoom;
+               x2 = width - rect->x1 * viewer->zoom;
+               y1 = height - rect->y2 * viewer->zoom;
+               y2 = height - rect->y1 * viewer->zoom;
+               selw = (x2 - x1);
+               selh = (y2 - y1);
+               y1 = height - y1;
+               y2 = height - y2;
+
+       } else if (viewer->rotate == 270) {
+               x1 = height - MAX(rect->y1,rect->y2) * viewer->zoom;
+               x2 = height - MIN(rect->y1,rect->y2) * viewer->zoom;
+               y1 = width - MIN(rect->x1,rect->x2) * viewer->zoom;
+               y2 = width - MAX(rect->x1,rect->x2) * viewer->zoom;
+               selw = (x2 - x1);
+               selh = (y1 - y2);
+       } else {
+               x1 = rect->x1 * viewer->zoom;
+               x2 = rect->x2 * viewer->zoom;
+               y1 = rect->y1 * viewer->zoom;
+               y2 = rect->y2 * viewer->zoom;
+               selw = (x2 - x1);
+               selh = (y2 - y1);
+               y1 = height - y1;
+               y2 = height - y2;
+       }
+                               
+       sel_pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 
+                                                       selw, selh);
+
+       gdk_pixbuf_fill(sel_pb, SELECTION_COLOR);
+                               
+       page_pb = gtk_image_get_pixbuf(GTK_IMAGE(viewer->pdf_view));
+                               
+       page_pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, 
+                                       FALSE, 8, 
+                               (int)(viewer->width * viewer->zoom), 
+                               (int)(viewer->height * viewer->zoom));  
+       
+       poppler_page_render_to_pixbuf(viewer->pdf_page, 
+                                       0, 
+                                       0, 
+                               (int)(viewer->width * viewer->zoom), 
+                               (int)(viewer->height * viewer->zoom), 
+                                       viewer->zoom, 
+                                       viewer->rotate, 
+                                       page_pb);
+                               
+       gdk_pixbuf_composite(sel_pb, page_pb, 
+                                       x1, y2, selw, selh, 0, 0, 
+                                       viewer->zoom, viewer->zoom, 
+                                       GDK_INTERP_BILINEAR, ALPHA_CHANNEL);
+                               
+       gtk_image_set_from_pixbuf(GTK_IMAGE(viewer->pdf_view), page_pb);
+       
+       pdf_viewer_scroll_to(viewer, MIN(x1,x2), MIN(y1,y2));
+
+       g_object_unref(G_OBJECT(sel_pb));
+       g_object_unref(G_OBJECT(page_pb));
+
+}
+
+static gboolean        pdf_viewer_text_search(MimeViewer *_viewer, gboolean backward,
+                                    const gchar *str, gboolean case_sens)
+{
+       PdfViewer *viewer = (PdfViewer *)_viewer;
+       GList *all_pages_results, *cur_page_results;
+       viewer->res_cnt = 0;
+
+       debug_print("pdf_viewer_text_search: %s\n", str);
+       main_window_cursor_wait(mainwindow_get_mainwindow());
+       
+       if (viewer->last_search && strcmp(str, viewer->last_search)) {
+               search_matches_free(viewer);
+               viewer->last_match = -1;
+               viewer->num_matches = 0;
+       } else if (!viewer->last_search) {
+               viewer->last_match = -1;
+               viewer->num_matches = 0;
+       }
+       /* It's a new search, build list of matches 
+        * across all pages */
+       if (viewer->last_match == -1) {
+               gint i; 
+               
+               for(i = 1; i <= viewer->num_pages; i++) {
+                       
+                       PopplerPage *pdf_page = poppler_document_get_page(viewer->pdf_doc, i - 1);
+                       viewer->page_results = poppler_page_find_text(pdf_page, str);
+                       
+                       if (viewer->page_results != NULL) {
+                               debug_print("page_results %p\n", viewer->page_results);
+                               /* store results for this page */
+                               gint num_res = 0;
+                               PageResult *res = g_new0(PageResult, 1);
+                               res->results = viewer->page_results;
+                               res->page_num = i;
+                               /* found text, prepend this page(faster than append) */
+                               viewer->text_found = g_list_prepend(viewer->text_found, res);
+                               num_res = g_list_length(viewer->page_results);
+                               debug_print("%d results on page %d\n", num_res, i);
+                               viewer->num_matches += num_res;
+                       }
+                       g_object_unref(G_OBJECT(pdf_page));
+               }
+
+               if (viewer->text_found == NULL) {
+                       main_window_cursor_normal(mainwindow_get_mainwindow());
+                       return FALSE;
+               }
+               /* put back the list in the correct order */
+               viewer->text_found = g_list_reverse(viewer->text_found);
+       } 
+       
+       if (!viewer->text_found) {
+               main_window_cursor_normal(mainwindow_get_mainwindow());
+               return FALSE;
+       } else {
+               viewer->last_search = g_strdup(str);
+       }
+       
+       if (backward) {
+               /* if backward, we have to initialize stuff to search 
+                * from the end */
+               viewer->res_cnt = viewer->num_matches-1;
+               if (viewer->last_match == -1) {
+                       viewer->last_match = viewer->num_matches+1;
+               }
+               all_pages_results = g_list_last(viewer->text_found);
+       } 
+       else {
+               all_pages_results = viewer->text_found;
+       }
+       
+       for(; all_pages_results; all_pages_results = (backward?all_pages_results->prev:all_pages_results->next)) {
+               
+               PageResult * page_results = (PageResult *)all_pages_results->data;
+
+               if (backward) {
+                       cur_page_results = g_list_last(page_results->results);
+               }
+               else {
+                       cur_page_results = page_results->results;
+               }
+
+               for(; cur_page_results; cur_page_results = (backward?cur_page_results->prev:cur_page_results->next)) {
+
+                       gboolean valid = FALSE;
+                       /* first valid result is the last+1 if searching
+                        * forward, last-1 if searching backward */
+                       if (backward) {
+                               valid = (viewer->res_cnt < viewer->last_match);
+                       }
+                       else {
+                               valid = (viewer->res_cnt > viewer->last_match);
+                       }
+                       if (valid) {
+                               pdf_viewer_render_selection(viewer, 
+                                       (PopplerRectangle *)cur_page_results->data,
+                                               page_results);
+                               main_window_cursor_normal(mainwindow_get_mainwindow());
+                               return TRUE;
+                       }
+                       
+                       if (backward) {
+                               viewer->res_cnt--;
+                       }
+                       else {
+                               viewer->res_cnt++;
+                       }
+               }
+       }
+       main_window_cursor_normal(mainwindow_get_mainwindow());
+       search_matches_free(viewer);
+       return FALSE;
+}
+
+static void pdf_viewer_get_document_index(PdfViewer *viewer, PopplerIndexIter *index_iter, GtkTreeIter *parentiter)
+{
+       PopplerAction *action;
+       PopplerIndexIter *child;
+       GtkTreeIter childiter;
+
+       debug_print("get document index\n");
+       do      {
+               gint page_num = 0;
+               
+               action = poppler_index_iter_get_action(index_iter);
+
+               if (action->type != POPPLER_ACTION_GOTO_DEST) {
+                       poppler_action_free(action);
+                       continue;
+               }
+
+               if (action->goto_dest.dest->type == POPPLER_DEST_XYZ || action->goto_dest.dest->type == POPPLER_DEST_FITH) {
+                       page_num = action->goto_dest.dest->page_num;
+               }
+#ifdef HAVE_POPPLER_DEST_NAMED
+               else if (action->goto_dest.dest->type == POPPLER_DEST_NAMED) {
+                       PopplerDest *dest = poppler_document_find_dest(
+                                       viewer->pdf_doc, action->goto_dest.dest->named_dest);
+                       if (dest->type != POPPLER_DEST_XYZ) {
+                               g_warning("couldn't figure out link\n");
+                               poppler_dest_free(dest);
+                               continue;
+                       }
+                       page_num = dest->page_num;
+                       poppler_dest_free(dest);
+               } 
+#endif
+               else {
+#ifdef HAVE_POPPLER_DEST_NAMED
+                       g_warning("unhandled link type %d\nplease contact developers\n", action->goto_dest.dest->type);
+#else
+                       g_warning("unhandled link type %d\nplease upgrade libpoppler-glib to 0.5.4\n", action->goto_dest.dest->type);
+#endif
+                       continue;
+               }
+               gtk_tree_store_append(GTK_TREE_STORE(viewer->index_model), &childiter, parentiter);
+               gtk_tree_store_set(GTK_TREE_STORE(viewer->index_model), &childiter,
+                                               INDEX_NAME, action->named.title,
+                                               INDEX_PAGE, page_num,
+                                               INDEX_TOP, action->goto_dest.dest->top,
+                                               -1);
+               poppler_action_free(action);
+               child = poppler_index_iter_get_child(index_iter);
+               if (child) {
+                       pdf_viewer_get_document_index(viewer, child, &childiter);
+                       poppler_index_iter_free(child);
+               }
+       }
+       while(poppler_index_iter_next(index_iter));
+}
+
+static void pdf_viewer_index_row_activated(GtkTreeView         *tree_view,
+                                       GtkTreePath             *path,
+                                       GtkTreeViewColumn       *column,
+                                       gpointer                 data)
+{
+       GtkTreeIter iter;
+       GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
+       PdfViewer *viewer = (PdfViewer *)data;
+       gint page_num = 0;
+       
+       debug_print("index_row_activated\n");
+       if (!gtk_tree_model_get_iter(model, &iter, path)) return;
+
+       gtk_tree_model_get(model, &iter, 
+                          INDEX_PAGE, &page_num,
+                          -1);
+
+       if (page_num > 0) {
+               gtk_spin_button_set_value(GTK_SPIN_BUTTON(viewer->cur_page),(gdouble)page_num);
+               debug_print("Page num: %d\n", page_num);
+       }
+       GTK_EVENTS_FLUSH();
+}
+
+/** Disable the index button if the document doesn't have an index */
+static void pdf_viewer_set_index_button_sensitive(PdfViewer *viewer)
+{
+       viewer->pdf_index  = poppler_index_iter_new(viewer->pdf_doc);
+       if (viewer->pdf_index) {        
+               if (!gtk_widget_is_sensitive(viewer->doc_index)) {
+                       gtk_widget_set_sensitive(viewer->doc_index, TRUE);
+               }
+       }
+       else {
+               gtk_widget_set_sensitive(viewer->doc_index, FALSE);
+       }
+
+    poppler_index_iter_free(viewer->pdf_index);
+    viewer->pdf_index = NULL;
+}
+
+static char * pdf_viewer_get_document_format_data(GTime utime) 
+{
+       time_t time = (time_t) utime;
+       struct tm t;
+       char s[256];
+       const char *fmt_hack = "%c";
+       size_t len;
+
+       if (time == 0 || !localtime_r(&time, &t)) return NULL;
+
+       len = strftime(s, sizeof(s), fmt_hack, &t);
+       
+       if (len == 0 || s[0] == '\0') return NULL;
+
+       return g_locale_to_utf8(s, -1, NULL, NULL, NULL);
+}
+
+#define ADD_TO_TABLE(LABEL, VALUE) \
+       label = gtk_label_new(LABEL); \
+       gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); \
+       gtk_misc_set_padding(GTK_MISC(label), 4, 0); \
+       gtk_table_attach(viewer->table_doc_info, label, 0, 1, row, row+1, GTK_EXPAND | GTK_FILL, 0, 0, 0); \
+       \
+       label = gtk_label_new(VALUE); \
+       gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
+       gtk_misc_set_padding(GTK_MISC(label), 4, 0); \
+       gtk_table_attach(viewer->table_doc_info, label, 1, 2, row, row+1, GTK_EXPAND | GTK_FILL, 0, 0, 0); \
+       row++;
+
+       
+static GtkTable * pdf_viewer_fill_info_table(PdfViewer *viewer)
+{
+       GtkWidget *label;
+       const gchar *title, *format, *author, *subject, *keywords, *creator, *producer;
+       gboolean linearized;
+       gchar *tmp;
+       gint row = 0;
+
+       GTime creation_date, mod_date;
+
+       PopplerPageLayout layout;
+       PopplerPageMode mode;
+       PopplerPermissions permissions;
+       PopplerViewerPreferences view_prefs;
+
+       title = format = author = subject = keywords = creator = producer = tmp = 0;
+
+       g_object_get(viewer->pdf_doc,
+                               "title", &title,
+                               "format", &format,
+                               "author", &author,
+                               "subject", &subject,
+                               "keywords", &keywords,
+                               "creation-date", &creation_date,
+                               "permissions", &permissions,
+                               "mod-date", &mod_date,
+                               "creator", &creator,
+                               "producer", &producer,  
+                               "linearized", &linearized,
+                               "page-mode", &mode,
+                               "page-layout", &layout,
+                               "viewer-preferences", &view_prefs,
+                               NULL);
+
+       viewer->table_doc_info = GTK_TABLE(gtk_table_new(13, 2, FALSE));
+
+       ADD_TO_TABLE(_("Filename:"), viewer->target_filename)
+       ADD_TO_TABLE(_("Size:"), to_human_readable(viewer->to_load->length))
+       ADD_TO_TABLE(NULL, NULL)
+       ADD_TO_TABLE(_("Title:"), title)
+       ADD_TO_TABLE(_("Subject:"), subject)
+       ADD_TO_TABLE(_("Author:"), author)
+       ADD_TO_TABLE(_("Keywords:"), keywords)
+       ADD_TO_TABLE(_("Creator:"), creator)
+       ADD_TO_TABLE(_("Producer:"), producer)
+
+       tmp = pdf_viewer_get_document_format_data(creation_date);
+       ADD_TO_TABLE(_("Created:"), tmp)
+       g_free(tmp);
+
+       tmp = pdf_viewer_get_document_format_data(mod_date);
+       ADD_TO_TABLE(_("Modified:"), tmp)
+       g_free(tmp);
+       
+       ADD_TO_TABLE(_("Format:"), format)
+       if (linearized) {
+               ADD_TO_TABLE(_("Optimized:"), _("Yes"))
+       }
+       else {
+               ADD_TO_TABLE(_("Optimized:"), _("No"))
+       }
+       //ADD_TO_TABLE(_("Page Mode:"), pdf_viewer_get_document_info_mode(mode)) 
+       //ADD_TO_TABLE(_("Page Layout:"), pdf_viewer_get_document_info_layout(layout))
+
+       return(GtkTable *) viewer->table_doc_info;
+}
+#undef ADD_TO_TABLE
+
+static FileType pdf_viewer_mimepart_get_type(MimeInfo *partinfo)
+{
+       gchar *content_type = NULL;
+       FileType type = TYPE_UNKNOWN;
+       debug_print("mimepart_get_type\n");
+       if ((partinfo->type == MIMETYPE_APPLICATION) &&
+       (!g_ascii_strcasecmp(partinfo->subtype, "octet-stream"))) {
+               
+               const gchar *filename;
+
+               filename = procmime_mimeinfo_get_parameter(partinfo, "filename");
+               
+                       if (filename == NULL)
+                               filename = procmime_mimeinfo_get_parameter(partinfo, "name");
+                       if (filename != NULL)
+                               content_type = procmime_get_mime_type(filename);
+       } 
+       else {
+               content_type = procmime_get_content_type_str(partinfo->type, partinfo->subtype);
+       }
+
+       if (content_type == NULL) type = TYPE_UNKNOWN;
+       else if (!strcmp(content_type, "application/pdf")) type = TYPE_PDF;
+       else if (!strcmp(content_type, "application/postscript")) type = TYPE_PS;
+       else type = TYPE_UNKNOWN;
+       
+       g_free(content_type);
+       return type;
+}
+
+/* Callbacks */
+static void pdf_viewer_button_first_page_cb(GtkButton *button, PdfViewer *viewer) 
+{
+       
+       gtk_spin_button_spin(GTK_SPIN_BUTTON(viewer->cur_page), GTK_SPIN_HOME, 1);
+}
+
+static void pdf_viewer_button_prev_page_cb(GtkButton *button, PdfViewer *viewer) 
+{
+       
+       gtk_spin_button_spin(GTK_SPIN_BUTTON(viewer->cur_page), GTK_SPIN_STEP_BACKWARD, 1);
+}
+
+static void pdf_viewer_button_next_page_cb(GtkButton *button, PdfViewer *viewer) 
+{
+       
+       gtk_spin_button_spin(GTK_SPIN_BUTTON(viewer->cur_page), GTK_SPIN_STEP_FORWARD, 1);
+}
+
+static void pdf_viewer_button_last_page_cb(GtkButton *button, PdfViewer *viewer) 
+{
+       
+       gtk_spin_button_spin(GTK_SPIN_BUTTON(viewer->cur_page), GTK_SPIN_END, 1);
+}
+
+static void pdf_viewer_spin_change_page_cb(GtkSpinButton *button, PdfViewer *viewer)
+{
+       pdf_viewer_update((MimeViewer *)viewer, 
+                       FALSE, 
+                       gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(viewer->cur_page)));
+}
+
+static void pdf_viewer_button_zoom_in_cb(GtkButton *button, PdfViewer *viewer) 
+{
+
+       gtk_spin_button_spin(GTK_SPIN_BUTTON(viewer->zoom_scroll), GTK_SPIN_STEP_FORWARD, ZOOM_FACTOR);
+}
+
+static void pdf_viewer_spin_zoom_scroll_cb(GtkSpinButton *button, PdfViewer *viewer)
+{
+       viewer->zoom = gtk_spin_button_get_value(GTK_SPIN_BUTTON(viewer->zoom_scroll));
+       pdf_viewer_update((MimeViewer *)viewer,
+                       FALSE,
+                       gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(viewer->cur_page)));
+}
+
+static void pdf_viewer_button_zoom_out_cb(GtkButton *button, PdfViewer *viewer) 
+{
+
+       gtk_spin_button_spin(GTK_SPIN_BUTTON(viewer->zoom_scroll), GTK_SPIN_STEP_BACKWARD, ZOOM_FACTOR);
+       
+}
+
+static void pdf_viewer_button_press_events_cb(GtkWidget *widget, GdkEventButton *event, PdfViewer *viewer) 
+{
+       gchar *uri;
+       #ifdef HAVE_POPPLER_DEST_NAMED
+       PopplerDest *dest;
+       #endif
+       static GdkCursor *hand_cur = NULL;
+       
+       if (!hand_cur) hand_cur = gdk_cursor_new(GDK_FLEUR);
+
+       /* Execute Poppler Links */
+       if (event->button == 1 && viewer->in_link) {
+               switch (viewer->link_action->type) {
+               case POPPLER_ACTION_UNKNOWN:
+                       debug_print("action unknown\n");
+                       break;
+               case POPPLER_ACTION_GOTO_DEST:
+                       if (viewer->link_action->goto_dest.dest->type == POPPLER_DEST_XYZ || 
+                                       viewer->link_action->goto_dest.dest->type == POPPLER_DEST_FITH) {
+                               gtk_spin_button_set_value(GTK_SPIN_BUTTON(viewer->cur_page), 
+                                                                         (gdouble)viewer->link_action->goto_dest.dest->page_num);
+                       }
+               #ifdef HAVE_POPPLER_DEST_NAMED
+                       else if (viewer->link_action->goto_dest.dest->type == POPPLER_DEST_NAMED) {
+                               dest = poppler_document_find_dest(
+                                       viewer->pdf_doc, viewer->link_action->goto_dest.dest->named_dest);
+                       if (dest->type != POPPLER_DEST_XYZ) {
+                               g_warning("couldn't figure out link\n");
+                               poppler_dest_free(dest);
+                               break;
+                       }
+                       gtk_spin_button_set_value(GTK_SPIN_BUTTON(viewer->cur_page), 
+                                                                         (gdouble)dest->page_num);
+                       poppler_dest_free(dest);
+               } 
+               #endif
+                       break;
+               case POPPLER_ACTION_GOTO_REMOTE:
+                       #ifdef HAVE_POPPLER_DEST_NAMED
+                       dest = poppler_document_find_dest(
+                                       viewer->pdf_doc, viewer->link_action->goto_remote.dest->named_dest);
+                       if (dest->type != POPPLER_DEST_XYZ) {
+                               g_warning ("couldn't figure out link\n");
+                               poppler_dest_free(dest);
+                               break;
+                       }
+                       gtk_spin_button_set_value(GTK_SPIN_BUTTON(viewer->cur_page),
+                                                                         (gdouble)dest->page_num);
+                       poppler_dest_free(dest);
+                       #endif
+                       break;
+               case POPPLER_ACTION_LAUNCH:
+                       debug_print("action launch not yet implemented\n");
+                       break;
+               case POPPLER_ACTION_URI:
+                       uri = g_strdup(viewer->link_action->uri.uri);
+                       if (!g_ascii_strncasecmp(uri, "mailto:", 7)) 
+                               compose_new(NULL, uri + 7, NULL);
+                       else 
+                               open_uri(uri, prefs_common_get_uri_cmd());
+                       g_free(uri);
+                       break;
+               case POPPLER_ACTION_NAMED:
+                       debug_print("action named not yet implemented\n");
+                       break;
+               case POPPLER_ACTION_MOVIE:
+                       debug_print("yoyoyo ;-) a movie?\n");
+                       break;
+               }
+               if (((MimeViewer *)viewer)->mimeview && 
+                       ((MimeViewer *)viewer)->mimeview->messageview && 
+                       ((MimeViewer *)viewer)->mimeview->messageview->window && 
+                       ((MimeViewer *)viewer)->mimeview->messageview->window->window) 
+                       gdk_window_set_cursor (((MimeViewer *)viewer)->mimeview->messageview->window->window, NULL);
+               else
+                       gdk_window_set_cursor (mainwindow_get_mainwindow()->window->window, NULL);
+       }
+
+       /* Init document to be scrolled with left mouse click */
+       if (event->button == 1 && !viewer->in_link) {
+               viewer->pdf_view_scroll = TRUE;
+               if (((MimeViewer *)viewer)->mimeview && 
+                       ((MimeViewer *)viewer)->mimeview->messageview && 
+                       ((MimeViewer *)viewer)->mimeview->messageview->window && 
+                       ((MimeViewer *)viewer)->mimeview->messageview->window->window) 
+                       gdk_window_set_cursor (((MimeViewer *)viewer)->mimeview->messageview->window->window, hand_cur);
+               else
+                       gdk_window_set_cursor (mainwindow_get_mainwindow()->window->window, hand_cur);
+
+               viewer->last_x = event->x;
+               viewer->last_y = event->y;
+               viewer->last_dir_x = 0;
+               viewer->last_dir_y = 0;
+       }
+}
+/* Set the normal cursor*/
+static void pdf_viewer_mouse_scroll_destroy_cb(GtkWidget *widget, GdkEventButton *event, PdfViewer *viewer) 
+{
+       
+       if (event->button == 1) {
+               viewer->pdf_view_scroll = FALSE;
+               if (((MimeViewer *)viewer)->mimeview && 
+                       ((MimeViewer *)viewer)->mimeview->messageview && 
+                       ((MimeViewer *)viewer)->mimeview->messageview->window && 
+                       ((MimeViewer *)viewer)->mimeview->messageview->window->window) 
+                       gdk_window_set_cursor (((MimeViewer *)viewer)->mimeview->messageview->window->window, NULL);
+               else
+                       gdk_window_set_cursor (mainwindow_get_mainwindow()->window->window, NULL);
+       }
+}
+
+static void pdf_viewer_move_events_cb(GtkWidget *widget, GdkEventMotion *event, PdfViewer *viewer) 
+{
+       /* Grab the document and scroll it with mouse */ 
+       if (viewer->pdf_view_scroll) {
+
+               viewer->pdf_view_vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewer->scrollwin));
+               viewer->pdf_view_hadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(viewer->scrollwin));
+                       
+                       if (event->x < viewer->last_x
+                                       && viewer->pdf_view_hadj->value < (viewer->pdf_view_hadj->upper - viewer->pdf_view_hadj->page_size)) {
+                               if (viewer->last_dir_x == -1) {
+                                       viewer->pdf_view_hadj->value += viewer->last_x - event->x; 
+                                       g_signal_emit_by_name(G_OBJECT(viewer->pdf_view_hadj),
+                                                               "value_changed", 0);
+                               }
+                               viewer->last_dir_x = -1;
+                       }
+                       else if (event->x > viewer->last_x
+                                       && viewer->pdf_view_hadj->value > 0.0)  {
+                               if (viewer->last_dir_x == +1) {
+                                       viewer->pdf_view_hadj->value += viewer->last_x - event->x; 
+                                       g_signal_emit_by_name(G_OBJECT(viewer->pdf_view_hadj),
+                                                               "value_changed", 0);
+                               }
+                               viewer->last_dir_x = +1;
+                       }
+
+                       if (event->y < viewer->last_y
+                                       && viewer->pdf_view_vadj->value < (viewer->pdf_view_vadj->upper - viewer->pdf_view_vadj->page_size)) {
+                               if (viewer->last_dir_y == -1) {
+                                       viewer->pdf_view_vadj->value += viewer->last_y - event->y; 
+                                       g_signal_emit_by_name(G_OBJECT(viewer->pdf_view_vadj),
+                                                               "value_changed", 0);
+                               }
+                               viewer->last_dir_y = -1;
+                       }
+                       else if (event->y > viewer->last_y
+                                       && viewer->pdf_view_vadj->value > 0.0)  {
+                               if (viewer->last_dir_y == +1) {
+                                       viewer->pdf_view_vadj->value += viewer->last_y - event->y; 
+                                       g_signal_emit_by_name(G_OBJECT(viewer->pdf_view_vadj),
+                                                               "value_changed", 0);
+                               }
+                               viewer->last_dir_y = +1;
+                       }
+                       viewer->last_x = event->x;
+                       viewer->last_y = event->y;
+                       GTK_EVENTS_FLUSH();
+               } 
+       else {  
+       /* Link Mapping */
+       static GList *l;
+       static GdkCursor *link_cur = NULL;
+       static GtkRequisition size;
+       static gdouble x,y, x1, y1, x2, y2;
+       gboolean ccur;
+       
+       viewer->pdf_view_vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewer->scrollwin));
+       viewer->pdf_view_hadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(viewer->scrollwin));
+       
+       if (!link_cur) link_cur = gdk_cursor_new(GDK_HAND2);
+
+       ccur = FALSE;
+       viewer->in_link = FALSE;        
+       for (l = viewer->link_map; l; l = g_list_next (l)) {
+               PopplerLinkMapping *lmapping;
+               lmapping = (PopplerLinkMapping *)l->data;
+
+               x1 = lmapping->area.x1;
+               y1 = lmapping->area.y1;
+               x2 = lmapping->area.x2;
+               y2 = lmapping->area.y2;
+               gtk_widget_size_request(viewer->pdf_view, &size);
+               
+               switch (viewer->rotate) {
+               case 0:
+               case 360:
+                               if (size.width != viewer->pdf_view_hadj->upper)
+                                       x = (event->x - (viewer->pdf_view_hadj->upper - size.width) / 2) / viewer->zoom;
+                               else
+                                       x = event->x / viewer->zoom;
+
+                               y = (viewer->pdf_view_vadj->upper - event->y) / viewer->zoom;
+                       break;
+               case 90:
+                               if (size.width != viewer->pdf_view_hadj->upper)
+                                       y = (event->x - (viewer->pdf_view_hadj->upper - size.width) / 2) / viewer->zoom;
+                               else
+                                       y = event->x / viewer->zoom;
+
+                               x = (event->y) / viewer->zoom;
+                       break;
+               case 180:
+                               if (size.width != viewer->pdf_view_hadj->upper)
+                                       x = ((viewer->pdf_view_hadj->upper -  event->x) - ((viewer->pdf_view_hadj->upper - size.width) / 2)) / viewer->zoom;
+                               else
+                                       x =  ((viewer->pdf_view_hadj->upper -  event->x) - (viewer->pdf_view_hadj->upper - size.width)) / viewer->zoom;
+
+                               y = (event->y) / viewer->zoom;
+                       break;
+               case 270:
+                               if (size.width != viewer->pdf_view_hadj->upper)
+                                       y = ((viewer->pdf_view_hadj->upper -  event->x) - ((viewer->pdf_view_hadj->upper - size.width) / 2)) / viewer->zoom;
+                               else
+                                       y =  ((viewer->pdf_view_hadj->upper -  event->x) - (viewer->pdf_view_hadj->upper - size.width)) / viewer->zoom;
+
+                               x = (viewer->pdf_view_vadj->upper - event->y) / viewer->zoom;
+                       break;
+               }
+
+               if ( (x > x1 && x < x2) && (y > y1 && y < y2) ) {
+                               viewer->in_link = TRUE;
+                       if (((MimeViewer *)viewer)->mimeview && 
+                               ((MimeViewer *)viewer)->mimeview->messageview && 
+                               ((MimeViewer *)viewer)->mimeview->messageview->window && 
+                               ((MimeViewer *)viewer)->mimeview->messageview->window->window) 
+                                       gdk_window_set_cursor (((MimeViewer *)viewer)->mimeview->messageview->window->window, link_cur);
+                               else
+                                       gdk_window_set_cursor (mainwindow_get_mainwindow()->window->window, link_cur);
+                               
+                               viewer->link_action = lmapping->action; 
+                               ccur = TRUE;
+               }
+               if (!ccur) {
+                       if (((MimeViewer *)viewer)->mimeview && 
+                               ((MimeViewer *)viewer)->mimeview->messageview && 
+                               ((MimeViewer *)viewer)->mimeview->messageview->window && 
+                               ((MimeViewer *)viewer)->mimeview->messageview->window->window) 
+                               gdk_window_set_cursor (((MimeViewer *)viewer)->mimeview->messageview->window->window, NULL);
+                       else
+                               gdk_window_set_cursor (mainwindow_get_mainwindow()->window->window, NULL);
+               }
+       }
+       g_free(l);
+       }
+}
+static gboolean pdf_viewer_scroll_cb(GtkWidget *widget, GdkEventScroll *event,
+                                   PdfViewer *viewer)
+{
+       GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewer->scrollwin));
+       static gboolean in_scroll_cb = FALSE;
+       gboolean handled = FALSE;
+       gint cur_p = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(viewer->cur_page));               
+
+       if (in_scroll_cb)
+               return FALSE;
+
+       in_scroll_cb = TRUE;
+
+       if (event->direction == GDK_SCROLL_UP &&
+           adj->value == adj->lower && 
+           cur_p > 1) {
+               gtk_spin_button_spin(GTK_SPIN_BUTTON(viewer->cur_page), GTK_SPIN_STEP_BACKWARD, 1);
+               adj->value = adj->upper - adj->page_size;
+               handled = TRUE;
+       } else if (event->direction == GDK_SCROLL_DOWN &&
+           adj->value + adj->page_size == adj->upper &&
+           cur_p < viewer->num_pages) {
+               gtk_spin_button_spin(GTK_SPIN_BUTTON(viewer->cur_page), GTK_SPIN_STEP_FORWARD, 1);
+               adj->value = 0.0;
+               handled = TRUE;
+       }
+       in_scroll_cb = FALSE;
+       return handled;
+}
+
+static void pdf_viewer_button_zoom_fit_cb(GtkButton *button, PdfViewer *viewer)
+{
+       GtkAllocation *allocation;
+       double xratio, yratio;
+       allocation = &(viewer->scrollwin->allocation);
+       debug_print("width: %d\n", allocation->width);
+       debug_print("height: %d\n", allocation->height);
+       xratio = allocation->width / viewer->width;
+       yratio = allocation->height / viewer->height;
+
+       if (xratio >= yratio) {
+               viewer->zoom = yratio;
+               gtk_spin_button_set_value(GTK_SPIN_BUTTON(viewer->zoom_scroll),viewer->zoom);
+       }
+       else {
+               viewer->zoom = xratio;
+               gtk_spin_button_set_value(GTK_SPIN_BUTTON(viewer->zoom_scroll),viewer->zoom);
+       }
+}
+
+static void pdf_viewer_button_zoom_width_cb(GtkButton *button, PdfViewer *viewer)
+{
+       GtkAllocation *allocation;
+       double xratio;
+       allocation = &(viewer->scrollwin->allocation);
+       debug_print("width: %d\n", allocation->width);
+       xratio = allocation->width / viewer->width;
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(viewer->zoom_scroll), xratio);
+}
+
+static void pdf_viewer_button_rotate_right_cb(GtkButton *button, PdfViewer *viewer)
+{
+       if (viewer->rotate == 360) {
+               viewer->rotate = 0;
+       }
+       
+       viewer->rotate += (gint) ROTATION;
+       pdf_viewer_update((MimeViewer *)viewer, FALSE,
+               gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(viewer->cur_page)));
+}
+
+static void pdf_viewer_button_rotate_left_cb(GtkButton *button, PdfViewer *viewer)
+{
+       if (viewer->rotate == 0) {
+               viewer->rotate = 360;
+       }
+       
+       viewer->rotate = abs(viewer->rotate -(gint) ROTATION);
+       pdf_viewer_update((MimeViewer *)viewer, FALSE,
+               gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(viewer->cur_page)));
+}
+
+/* Show/Hide the index pane */
+static void pdf_viewer_show_document_index_cb(GtkButton *button, PdfViewer *viewer)
+{
+       if (!viewer->pdf_index) {
+               viewer->pdf_index = poppler_index_iter_new(viewer->pdf_doc);
+       }
+       
+       gtk_tree_store_clear(GTK_TREE_STORE(viewer->index_model));
+       
+       if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(viewer->doc_index))) {
+               pdf_viewer_get_document_index(viewer,(PopplerIndexIter *) viewer->pdf_index, NULL);
+               gtk_widget_show(GTK_WIDGET(viewer->frame_index));
+       }
+       else {
+               pdf_viewer_hide_index_pane(viewer);
+       }
+               
+}
+
+static void pdf_viewer_button_document_info_cb(GtkButton *button, PdfViewer *viewer)
+{
+       gchar *buf;
+       buf = g_strdup_printf(_("PDF Viewer Plugin"));
+
+       alertpanel_full(buf, NULL, GTK_STOCK_CLOSE, NULL, NULL,
+                                       FALSE,(GtkWidget *) pdf_viewer_fill_info_table(viewer), 
+                                       ALERT_NOTICE, 
+                                       G_ALERTDEFAULT);
+       g_free(buf);
+}
+
+/*
+static const char * poppler_get_document_info_mode(PopplerPageMode mode)
+{
+       GEnumValue *enum_value;
+
+       enum_value = g_enum_get_value((GEnumClass *) g_type_class_peek(POPPLER_TYPE_PAGE_MODE), mode);
+       return(gchar *) enum_value->value_name;
+}
+static const char * poppler_get_document_info_layout(PopplerPageLayout layout)
+{
+
+       GEnumValue *enum_value;
+
+       enum_value = g_enum_get_value((GEnumClass *) g_type_class_peek(POPPLER_TYPE_PAGE_LAYOUT), layout);
+       return(gchar *) enum_value->value_name;
+}
+*/
+static void pdf_viewer_show_controls(PdfViewer *viewer, gboolean show)
+{
+       if (show) {
+               gtk_widget_show(viewer->first_page);
+               gtk_widget_show(viewer->cur_page);
+               gtk_widget_show(viewer->prev_page);
+               gtk_widget_show(viewer->next_page);
+               gtk_widget_show(viewer->last_page);
+               gtk_widget_show(viewer->zoom_in);
+               gtk_widget_show(viewer->zoom_out);
+               gtk_widget_show(viewer->zoom_fit);
+               gtk_widget_show(viewer->zoom_width);
+               gtk_widget_show(viewer->zoom_scroll);
+               gtk_widget_show(viewer->widgets_table);
+               gtk_widget_show(viewer->rotate_right);
+               gtk_widget_show(viewer->rotate_left);
+               gtk_widget_show(viewer->doc_info);
+               gtk_widget_show(viewer->doc_index);
+       } else {
+               gtk_widget_hide(viewer->first_page);
+               gtk_widget_hide(viewer->cur_page);
+               gtk_widget_hide(viewer->prev_page);
+               gtk_widget_hide(viewer->next_page);
+               gtk_widget_hide(viewer->last_page);
+               gtk_widget_hide(viewer->zoom_in);
+               gtk_widget_hide(viewer->zoom_out);
+               gtk_widget_hide(viewer->zoom_fit);
+               gtk_widget_hide(viewer->zoom_width);
+               gtk_widget_hide(viewer->widgets_table);
+               gtk_widget_hide(viewer->rotate_right);
+               gtk_widget_hide(viewer->rotate_left);
+               gtk_widget_hide(viewer->doc_info);
+               gtk_widget_hide(viewer->doc_index);
+               gtk_widget_hide(viewer->zoom_scroll);
+       }
+}
+/** Render the current page, page_num on the viewer */
+static void pdf_viewer_update(MimeViewer *_viewer, gboolean reload_file, int page_num) 
+{
+
+       PdfViewer *viewer = (PdfViewer *) _viewer;
+       GError *error;
+       gchar *tmpfile = NULL;
+       gchar *tmp;
+
+       debug_print("pdf_viewer_update\n");
+
+       error = NULL;
+       if (reload_file) {
+               if (viewer->pdf_doc) {
+                       g_object_unref(G_OBJECT(viewer->pdf_doc));
+                       viewer->pdf_doc = NULL;
+               }
+               
+               if (pdf_viewer_mimepart_get_type(viewer->to_load) == TYPE_PS) {
+                       stock_pixbuf_gdk(viewer->hbox, 
+                                       STOCK_PIXMAP_MIME_PS, 
+                                       &viewer->icon_pixbuf);
+                       gtk_image_set_from_pixbuf(GTK_IMAGE(viewer->icon_type),
+                                                                       viewer->icon_pixbuf);
+               } 
+               else if (pdf_viewer_mimepart_get_type(viewer->to_load) == TYPE_PDF) {
+                       stock_pixbuf_gdk(viewer->hbox, 
+                       STOCK_PIXMAP_MIME_PDF, 
+                       &viewer->icon_pixbuf);
+                       gtk_image_set_from_pixbuf(GTK_IMAGE(viewer->icon_type), 
+                                                                       viewer->icon_pixbuf);
+               } 
+               else {
+                       stock_pixbuf_gdk(viewer->hbox, 
+                       STOCK_PIXMAP_MIME_APPLICATION, 
+                       &viewer->icon_pixbuf);
+                       gtk_image_set_from_pixbuf(GTK_IMAGE(viewer->icon_type), 
+                                                                       viewer->icon_pixbuf);
+               }
+
+               gtk_label_set_text(GTK_LABEL(viewer->doc_label), _("Loading..."));      
+               pdf_viewer_show_controls(viewer, FALSE);
+               main_window_cursor_wait(mainwindow_get_mainwindow());
+
+               GTK_EVENTS_FLUSH();
+
+               if (pdf_viewer_mimepart_get_type(viewer->to_load) == TYPE_PS) {
+                       gchar *cmdline = NULL, *tmp = NULL, *gspath = NULL;
+                       gint result = 0;
+
+                       if ((gspath = g_find_program_in_path("gs")) != NULL) {
+                               g_free(gspath);
+                               /* convert postscript to pdf */
+                               tmpfile = get_tmp_file();
+                               cmdline = g_strdup_printf(
+                                       "gs -dSAFER -dCompatibilityLevel=1.2 -q -dNOPAUSE -dBATCH "
+                                         "-sDEVICE=pdfwrite -sOutputFile=%s -c .setpdfwrite -f \"%s\"",
+                                       tmpfile, viewer->filename);
+                               result = execute_command_line(cmdline, FALSE);
+                               if (result == 0) {
+                                       tmp = g_filename_to_uri(tmpfile, NULL, NULL);
+                                       viewer->pdf_doc = poppler_document_new_from_file( tmp, NULL, &error);
+                                       g_free(tmp);
+                               } 
+                               else {
+                                       g_warning("gs conversion failed: %s returned %d\n", cmdline, result);
+                                       tmp = g_strdup_printf("gs: err %d", result);
+                                       alertpanel_warning("%s", tmp);
+                                       g_free(tmp);
+                               }
+       
+                               g_free(cmdline);
+                               claws_unlink(tmpfile);
+                               g_free(tmpfile);
+                       }
+                       else {
+                               g_warning("gs conversion disabled: gs binary was not found\n");
+                               alertpanel_warning("PostScript view disabled: required gs program not found");
+                               result = 1;
+                                       
+                       }
+                       if (result != 0) {
+                               main_window_cursor_normal(mainwindow_get_mainwindow());
+                               return;
+                       }
+               }   
+               else {
+                       viewer->pdf_doc = poppler_document_new_from_file( viewer->fsname, NULL, &error);
+               }
+               
+               viewer->num_pages = poppler_document_get_n_pages(viewer->pdf_doc);
+
+               g_signal_handlers_block_by_func(G_OBJECT(viewer->cur_page), pdf_viewer_spin_change_page_cb,(gpointer *)viewer);
+               gtk_spin_button_set_range(GTK_SPIN_BUTTON(viewer->cur_page), 
+                                                                       1, 
+                                                               (gdouble)viewer->num_pages );
+
+               g_signal_handlers_unblock_by_func(G_OBJECT(viewer->cur_page), pdf_viewer_spin_change_page_cb,(gpointer *)viewer);
+               gtk_spin_button_spin(GTK_SPIN_BUTTON(viewer->cur_page), GTK_SPIN_HOME, 1);
+               tmp = g_strdup_printf(_("%s Document"),pdf_viewer_mimepart_get_type(viewer->to_load) == TYPE_PDF ? "PDF":"Postscript");
+               CLAWS_SET_TIP(
+                               GTK_WIDGET(viewer->icon_type_ebox),
+                               tmp);
+               g_free(tmp);
+
+               tmp = g_strdup_printf(_("of %d"), viewer->num_pages);
+               gtk_label_set_text(GTK_LABEL(viewer->doc_label), tmp);
+               g_free(tmp);
+
+               pdf_viewer_show_controls(viewer, TRUE);
+               main_window_cursor_normal(mainwindow_get_mainwindow());
+       } 
+       if (viewer->pdf_doc == NULL) {
+               strretchomp(error->message);
+               stock_pixbuf_gdk(viewer->hbox, 
+                               STOCK_PIXMAP_MIME_APPLICATION, 
+                               &viewer->icon_pixbuf);
+
+               gtk_image_set_from_pixbuf(GTK_IMAGE(viewer->icon_type), viewer->icon_pixbuf);
+               alertpanel_error("%s", error->message);
+       
+               pdf_viewer_show_controls(viewer, FALSE);
+               g_error_free(error);
+               return;
+       }
+
+       if (page_num == 1) { 
+               gtk_widget_set_sensitive(viewer->first_page, FALSE);
+               gtk_widget_set_sensitive(viewer->prev_page, FALSE);
+       }
+       else {
+               gtk_widget_set_sensitive(viewer->first_page, TRUE);
+               gtk_widget_set_sensitive(viewer->prev_page, TRUE);
+       }
+
+       if (page_num == viewer->num_pages) { 
+               gtk_widget_set_sensitive(viewer->last_page, FALSE);
+               gtk_widget_set_sensitive(viewer->next_page, FALSE);
+       }
+       else {
+               gtk_widget_set_sensitive(viewer->last_page, TRUE);
+               gtk_widget_set_sensitive(viewer->next_page, TRUE);
+       }
+
+       /* check for the index if exists */
+       pdf_viewer_set_index_button_sensitive((PdfViewer *) viewer);
+       
+       if (page_num > 0 && page_num <= viewer->num_pages) {
+
+               GTK_EVENTS_FLUSH();
+               
+               if (viewer->pdf_page) {
+                       g_object_unref(G_OBJECT(viewer->pdf_page));
+               }
+                       
+               viewer->pdf_page = poppler_document_get_page(viewer->pdf_doc, page_num - 1);
+
+               if (viewer->pdf_page == NULL) {
+                       g_warning("Page not found\n");
+                       return;
+               }   
+       
+               if (viewer->rotate == 90 || viewer->rotate == 270) {
+                       poppler_page_get_size(viewer->pdf_page, &viewer->height, &viewer->width);
+               } 
+               else {
+                       poppler_page_get_size(viewer->pdf_page, &viewer->width, &viewer->height);
+               }
+
+               if (viewer->last_rect && viewer->last_page_result &&
+                   viewer->last_page_result->page_num == page_num) {
+                       pdf_viewer_render_selection(viewer, viewer->last_rect, viewer->last_page_result);
+               }
+               else {
+                       pdf_viewer_render_page(viewer->pdf_page, viewer->pdf_view, viewer->width, 
+                                                                       viewer->height, viewer->zoom, viewer->rotate);
+
+               }
+       
+       /* Get Links Mapping */
+       if (viewer->link_map) {
+               poppler_page_free_link_mapping(viewer->link_map);
+       }
+       viewer->link_map = poppler_page_get_link_mapping(viewer->pdf_page);
+       
+       }
+}
+
+
+static void pdf_viewer_show_mimepart(MimeViewer *_viewer, const gchar *infile,
+                               MimeInfo *partinfo)
+{
+       PdfViewer *viewer = (PdfViewer *) _viewer;
+       gchar buf[4096];
+       const gchar *charset = NULL;
+       MessageView *messageview = ((MimeViewer *)viewer)->mimeview 
+                                       ?((MimeViewer *)viewer)->mimeview->messageview 
+                                       : NULL;
+
+       viewer->rotate = 0;
+       viewer->to_load = partinfo;
+
+       memset(buf, 0, sizeof(buf));
+       messageview->updating = TRUE;
+       debug_print("pdf_viewer_show_mimepart\n");
+
+       if (viewer->filename != NULL) {
+               claws_unlink(viewer->filename);
+               g_free(viewer->filename);
+               viewer->filename = NULL;
+       }
+       
+       viewer->mimeinfo = NULL;
+       
+       if (partinfo) {
+               viewer->target_filename = procmime_get_part_file_name(partinfo);
+               viewer->filename = procmime_get_tmp_file_name(partinfo);
+               viewer->fsname = g_filename_to_uri(viewer->filename, NULL, NULL);
+       }
+       
+       if (partinfo && !(procmime_get_part(viewer->filename, partinfo) < 0)) {
+
+               if (_viewer && _viewer->mimeview && 
+                               _viewer->mimeview->messageview->forced_charset) {
+                       charset = _viewer->mimeview->messageview->forced_charset;
+               }
+               else {
+                       charset = procmime_mimeinfo_get_parameter(partinfo, "charset");
+               }
+               if (charset == NULL) {
+                       charset = conv_get_locale_charset_str();
+               }
+
+               debug_print("using charset %s\n", charset);
+               
+               viewer->mimeinfo = partinfo;
+       }
+       
+       pdf_viewer_update((MimeViewer *)viewer, TRUE, 1);
+       
+       messageview->updating = FALSE;
+}
+
+static void pdf_viewer_clear(MimeViewer *_viewer)
+{
+       PdfViewer *viewer = (PdfViewer *) _viewer;
+       GtkAdjustment *vadj;
+               
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(viewer->doc_index), FALSE);
+       gtk_widget_hide(viewer->frame_index);
+
+       debug_print("pdf_viewer_clear\n");
+       viewer->to_load = NULL;
+       
+       if (viewer->pdf_doc) {
+               g_object_unref(G_OBJECT(viewer->pdf_doc));
+               viewer->pdf_doc = NULL;
+       }
+       
+       vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewer->scrollwin));
+       vadj->value = 0.0;
+       g_signal_emit_by_name(G_OBJECT(vadj), "value-changed", 0);
+       vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewer->scrollwin_index));
+       vadj->value = 0.0;
+       g_signal_emit_by_name(G_OBJECT(vadj), "value-changed", 0);
+       gtk_tree_store_clear(GTK_TREE_STORE(viewer->index_model));
+       gtk_image_set_from_pixbuf(GTK_IMAGE(viewer->pdf_view), NULL);
+}
+
+static void pdf_viewer_destroy(MimeViewer *_viewer)
+{
+       PdfViewer *viewer = (PdfViewer *) _viewer;
+
+       debug_print("pdf_viewer_destroy\n");
+       
+       if (viewer->pdf_index) poppler_index_iter_free(viewer->pdf_index);
+
+       poppler_page_free_link_mapping (viewer->link_map);
+       g_object_unref(GTK_WIDGET(viewer->vbox));
+       g_object_unref(GTK_WIDGET(viewer->pdf_view));
+       g_object_unref(GTK_WIDGET(viewer->doc_index_pane));
+       g_object_unref(GTK_WIDGET(viewer->scrollwin));
+       g_object_unref(GTK_WIDGET(viewer->scrollwin_index));
+       claws_unlink(viewer->filename);
+       g_free(viewer->filename);
+       g_free(viewer);
+}
+
+static gboolean pdf_viewer_scroll_page(MimeViewer *_viewer, gboolean up)
+{
+       PdfViewer *viewer = (PdfViewer *)_viewer;
+       GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(
+                               GTK_SCROLLED_WINDOW(viewer->scrollwin));
+       
+       gint cur_p = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(viewer->cur_page));
+       
+       if (viewer->pdf_view == NULL) return FALSE;
+
+       if (!gtkutils_scroll_page(GTK_WIDGET(viewer->pdf_view), vadj, up)) {
+               if (!up && cur_p != viewer->num_pages) {
+                       gtk_spin_button_spin(GTK_SPIN_BUTTON(viewer->cur_page), GTK_SPIN_STEP_FORWARD, 1);
+                       vadj = gtk_scrolled_window_get_vadjustment(
+                                       GTK_SCROLLED_WINDOW(viewer->scrollwin));
+                       vadj->value = 0.0;
+                       g_signal_emit_by_name(G_OBJECT(vadj), "value-changed", 0);
+                       return TRUE;
+               } 
+               else if (up && cur_p != 1) {
+                       gtk_spin_button_spin(GTK_SPIN_BUTTON(viewer->cur_page), GTK_SPIN_STEP_BACKWARD, 1);
+                       vadj = gtk_scrolled_window_get_vadjustment(
+                                       GTK_SCROLLED_WINDOW(viewer->scrollwin));
+                       vadj->value = vadj->upper - vadj->page_size;
+                       g_signal_emit_by_name(G_OBJECT(vadj), "value-changed", 0);
+                       return TRUE;
+               } 
+               return FALSE;
+       } 
+       else return TRUE;
+}
+
+static void pdf_viewer_scroll_one_line(MimeViewer *_viewer, gboolean up)
+{
+       PdfViewer *viewer = (PdfViewer *)_viewer;
+       GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(
+                               GTK_SCROLLED_WINDOW(viewer->scrollwin));
+       gint cur_p;
+       cur_p = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(viewer->cur_page));
+
+       if (viewer->pdf_view == NULL) return; 
+               debug_print("up: %d\n", up);    
+               if (vadj->value <(vadj->upper - vadj->page_size))  {
+                       gtkutils_scroll_one_line(GTK_WIDGET(viewer->pdf_view), vadj, up);
+               }
+               else {
+                       if (cur_p != viewer->num_pages) {
+                               pdf_viewer_scroll_page((MimeViewer *)viewer, up);
+                       }
+               }
+
+}
+
+#define BUTTON_H_PADDING 3
+#define ADD_BUTTON_TO_TABLE(widget, stock_image) \
+       widget = gtk_button_new(); \
+       img = gtk_image_new_from_stock(stock_image, GTK_ICON_SIZE_MENU); \
+       gtk_button_set_image(GTK_BUTTON(widget), img); \
+       gtk_table_attach(GTK_TABLE(viewer->widgets_table), GTK_WIDGET(widget), \
+                               col, col+1, 0, 1, 0, 0, BUTTON_H_PADDING, 0); \
+       col++;
+
+#define ADD_SEP_TO_TABLE \
+       sep = gtk_label_new(""); \
+       gtk_table_attach(GTK_TABLE(viewer->widgets_table), GTK_WIDGET(sep), \
+                                       col, col+1, 0, 1, 0, 0, 0, 0); \
+       gtk_table_set_col_spacing(GTK_TABLE(viewer->widgets_table), col, 3*BUTTON_H_PADDING); \
+       col++;
+
+#if GTK_CHECK_VERSION(2,10,0) && POPPLER_HAS_CAIRO && !USE_LIBGNOMEPRINT
+static PangoContext *pdf_viewer_get_pango_context(gpointer data)
+{
+       return NULL;
+}
+
+static gpointer pdf_viewer_get_data_to_print(gpointer data, gint sel_start, gint sel_end)
+{
+       return NULL; /* we don't need it */
+}
+
+static void pdf_viewer_cb_begin_print(GtkPrintOperation *op, GtkPrintContext *context,
+                          gpointer user_data)
+{
+  PrintData *print_data;
+  PopplerDocument *pdf_doc;
+  gint n_pages = 0;
+  print_data = (PrintData*) user_data;
+  pdf_doc = (PopplerDocument *)printing_get_renderer_data(print_data);
+
+  debug_print("Preparing print job...\n");
+
+  n_pages = poppler_document_get_n_pages(pdf_doc);
+  printing_set_n_pages(print_data, n_pages);
+  gtk_print_operation_set_n_pages(op, n_pages);
+
+  debug_print("Starting print job...\n");
+}
+
+static void pdf_viewer_cb_draw_page(GtkPrintOperation *op, GtkPrintContext *context,
+                        int page_nr, gpointer user_data)
+{
+  cairo_t *cr;
+  PrintData *print_data;
+  PopplerDocument *pdf_doc;
+  PopplerPage *pdf_page;
+  
+  print_data = (PrintData*) user_data;
+  pdf_doc = (PopplerDocument *)printing_get_renderer_data(print_data);
+  pdf_page = poppler_document_get_page(pdf_doc, page_nr);
+
+  cr = gtk_print_context_get_cairo_context(context);
+  cairo_scale(cr, printing_get_zoom(print_data), printing_get_zoom(print_data));
+  cairo_set_source_rgb(cr, 0., 0., 0.);
+
+  poppler_page_render(pdf_page, cr);
+
+  g_object_unref(G_OBJECT(pdf_page));
+
+  debug_print("Sent page %d to printer\n", page_nr+1);
+}
+
+static void pdf_viewer_print(MimeViewer *mviewer)
+{
+       PdfViewer *viewer = (PdfViewer *)mviewer;
+       PrintRenderer *pdf_renderer = g_new0(PrintRenderer, 1);
+       MainWindow *mainwin = mainwindow_get_mainwindow();
+       
+       pdf_renderer->get_pango_context = pdf_viewer_get_pango_context;
+       pdf_renderer->get_data_to_print = pdf_viewer_get_data_to_print;
+       pdf_renderer->cb_begin_print    = pdf_viewer_cb_begin_print;
+       pdf_renderer->cb_draw_page      = pdf_viewer_cb_draw_page;
+
+       printing_print_full(mainwin ? GTK_WINDOW(mainwin->window):NULL, 
+                       pdf_renderer, viewer->pdf_doc, -1, -1);
+       
+       g_free(pdf_renderer);
+}
+#endif
+
+static MimeViewer *pdf_viewer_create(void)
+{
+       PdfViewer *viewer;
+       GtkTreeViewColumn *column;
+       GtkCellRenderer *renderer;
+       GtkTreeStore *tree_store;
+       GtkWidget *sep;
+       GtkWidget *img;
+       gint col = 0;
+
+       viewer = g_new0(PdfViewer, 1);
+       debug_print("pdf_viewer_create\n");
+    
+       viewer->last_x = 0;
+       viewer->last_y = 0;
+       viewer->mimeviewer.factory = &pdf_viewer_factory;
+       viewer->mimeviewer.get_widget = pdf_viewer_get_widget;
+       viewer->mimeviewer.show_mimepart = pdf_viewer_show_mimepart;
+       viewer->mimeviewer.clear_viewer = pdf_viewer_clear;
+       viewer->mimeviewer.destroy_viewer = pdf_viewer_destroy;
+       viewer->mimeviewer.text_search = pdf_viewer_text_search;
+       viewer->mimeviewer.scroll_page = pdf_viewer_scroll_page;
+       viewer->mimeviewer.scroll_one_line = pdf_viewer_scroll_one_line;
+#if GTK_CHECK_VERSION(2,10,0) && POPPLER_HAS_CAIRO && !USE_LIBGNOMEPRINT
+       viewer->mimeviewer.print = pdf_viewer_print;
+#endif
+       viewer->scrollwin = gtk_scrolled_window_new(NULL, NULL);
+       viewer->scrollwin_index = gtk_scrolled_window_new(NULL, NULL);
+       viewer->pdf_view_ebox = gtk_event_box_new();
+       gtk_event_box_set_visible_window(GTK_EVENT_BOX(viewer->pdf_view_ebox), FALSE);
+                                                       
+       viewer->mimeinfo  = NULL;
+
+       viewer->pdf_view = gtk_image_new();
+       gtk_widget_set_events(viewer->pdf_view_ebox,
+                                               GDK_BUTTON_RELEASE_MASK
+                                               | GDK_POINTER_MOTION_MASK
+                                               | GDK_BUTTON_PRESS_MASK
+                                               | GDK_BUTTON_MOTION_MASK
+                                           );
+       gtk_container_add (GTK_CONTAINER(viewer->pdf_view_ebox), viewer->pdf_view);
+       viewer->icon_type = gtk_image_new();
+       viewer->icon_type_ebox = gtk_event_box_new();
+
+       gtk_container_add(GTK_CONTAINER(viewer->icon_type_ebox), viewer->icon_type);
+
+       viewer->doc_label = gtk_label_new("");
+
+       viewer->widgets_table = gtk_table_new(1, 1, FALSE);
+
+       viewer->doc_index_pane = gtk_hpaned_new();
+
+       viewer->frame_index = gtk_frame_new(NULL);
+       gtk_frame_set_shadow_type(GTK_FRAME(viewer->frame_index), GTK_SHADOW_IN);
+       gtk_widget_set_size_request(viewer->frame_index, 18, -1);
+       gtk_frame_set_label(GTK_FRAME(viewer->frame_index), _("Document Index"));
+
+#if !(GTK_CHECK_VERSION(2,12,0))
+       viewer->button_bar_tips = tips;
+#endif
+
+       ADD_SEP_TO_TABLE
+       ADD_BUTTON_TO_TABLE(viewer->first_page, GTK_STOCK_GOTO_FIRST)
+       ADD_BUTTON_TO_TABLE(viewer->prev_page, GTK_STOCK_GO_BACK)
+       viewer->cur_page = gtk_spin_button_new_with_range(0.0, 0.0, 1.0);
+       viewer->zoom_scroll = gtk_spin_button_new_with_range(0.20, 8.0, 0.20);
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(viewer->zoom_scroll), 1.0);
+       viewer->zoom = 1.0;
+       gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(viewer->cur_page), TRUE);
+       gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(viewer->zoom_scroll), TRUE);
+       gtk_table_attach(GTK_TABLE(viewer->widgets_table), GTK_WIDGET(viewer->cur_page),
+                                       col, col+1, 
+                                       0, 1, 0, 0, 
+                                       BUTTON_H_PADDING, 
+                                       0);
+       col++;
+       gtk_table_attach(GTK_TABLE(viewer->widgets_table), GTK_WIDGET(viewer->doc_label),
+                                       col, col+1, 
+                                       0, 1, 0, 0, 
+                                       BUTTON_H_PADDING, 
+                                       0);
+       col++;
+
+       ADD_BUTTON_TO_TABLE(viewer->next_page, GTK_STOCK_GO_FORWARD)
+       ADD_BUTTON_TO_TABLE(viewer->last_page, GTK_STOCK_GOTO_LAST)
+       ADD_SEP_TO_TABLE
+       ADD_BUTTON_TO_TABLE(viewer->zoom_fit, GTK_STOCK_ZOOM_FIT)
+       ADD_BUTTON_TO_TABLE(viewer->zoom_in, GTK_STOCK_ZOOM_IN)
+       gtk_table_attach(GTK_TABLE(viewer->widgets_table), GTK_WIDGET(viewer->zoom_scroll),
+                                       col, col+1, 
+                                       0, 1, 0, 0, 
+                                       BUTTON_H_PADDING, 
+                                       0);
+       col++;
+       ADD_BUTTON_TO_TABLE(viewer->zoom_out, GTK_STOCK_ZOOM_OUT)
+       ADD_BUTTON_TO_TABLE(viewer->zoom_width, GTK_STOCK_FULLSCREEN)
+       ADD_SEP_TO_TABLE
+       ADD_BUTTON_TO_TABLE(viewer->rotate_left, GTK_STOCK_UNDO)
+       ADD_BUTTON_TO_TABLE(viewer->rotate_right, GTK_STOCK_REDO)
+       ADD_SEP_TO_TABLE
+       ADD_BUTTON_TO_TABLE(viewer->doc_info, GTK_STOCK_INFO)
+
+       viewer->doc_index = GTK_WIDGET(gtk_toggle_tool_button_new_from_stock(GTK_STOCK_INDEX));
+       gtk_widget_set_size_request(GTK_WIDGET(viewer->doc_index), 26, 26);
+       gtk_table_attach(GTK_TABLE(viewer->widgets_table), GTK_WIDGET(viewer->doc_index),
+                                       col, col+1, 
+                                       0, 1, 0, 0, 
+                                       BUTTON_H_PADDING, 
+                                       0);
+       col++;
+       
+       gtk_scrolled_window_set_policy(
+                       GTK_SCROLLED_WINDOW(viewer->scrollwin), 
+                       GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+       gtk_scrolled_window_set_shadow_type(
+                       GTK_SCROLLED_WINDOW(viewer->scrollwin),
+                       GTK_SHADOW_IN);
+
+       gtk_scrolled_window_add_with_viewport(
+                       GTK_SCROLLED_WINDOW(viewer->scrollwin),
+                       viewer->pdf_view_ebox);
+
+       viewer->vbox = gtk_vbox_new(FALSE, 4);
+       viewer->hbox = gtk_hbox_new(FALSE, 4);
+
+    /* treeview */
+       tree_store = gtk_tree_store_new(N_INDEX_COLUMNS,
+                                       G_TYPE_STRING,
+                                       G_TYPE_INT,
+                                       G_TYPE_DOUBLE);
+
+       viewer->index_list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(tree_store));
+       renderer = gtk_cell_renderer_text_new();
+       column = gtk_tree_view_column_new_with_attributes(_("Name"),  renderer, "text", 0,  NULL);
+       gtk_tree_view_append_column(GTK_TREE_VIEW(viewer->index_list), column);         
+       gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(viewer->index_list), FALSE);
+       
+       viewer->index_model = GTK_TREE_MODEL(tree_store);
+
+       gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(viewer->index_list)), 
+                                                               GTK_SELECTION_SINGLE);
+
+       g_signal_connect(G_OBJECT(viewer->index_list), "row_activated",
+                        G_CALLBACK(pdf_viewer_index_row_activated),
+                                        viewer);
+
+       gtk_scrolled_window_set_policy(
+                       GTK_SCROLLED_WINDOW(viewer->scrollwin_index), 
+                       GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+       gtk_scrolled_window_set_shadow_type(
+                       GTK_SCROLLED_WINDOW(viewer->scrollwin_index),
+                       GTK_SHADOW_IN);
+
+       gtk_scrolled_window_add_with_viewport(
+                       GTK_SCROLLED_WINDOW(viewer->scrollwin_index),
+                       viewer->index_list);
+
+       /* end treeview */
+
+       stock_pixbuf_gdk(viewer->hbox, 
+                       STOCK_PIXMAP_MIME_TEXT_PLAIN, 
+                       &viewer->icon_pixbuf);
+
+       gtk_image_set_from_pixbuf(GTK_IMAGE(viewer->icon_type), 
+                               viewer->icon_pixbuf);
+
+       /* pack widgets*/
+       gtk_box_pack_start(GTK_BOX(viewer->hbox), viewer->icon_type_ebox, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(viewer->hbox), viewer->widgets_table, FALSE, FALSE, 0);
+
+       gtk_container_add(GTK_CONTAINER(viewer->frame_index), viewer->scrollwin_index);
+
+       gtk_paned_pack1(GTK_PANED(viewer->doc_index_pane), viewer->frame_index, FALSE, FALSE);
+       gtk_paned_pack2(GTK_PANED(viewer->doc_index_pane), viewer->scrollwin, FALSE, FALSE);
+
+       gtk_box_pack_start(GTK_BOX(viewer->vbox), viewer->hbox, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(viewer->vbox), viewer->doc_index_pane, TRUE, TRUE, 0);
+       /* show widgets */
+       gtk_widget_show(GTK_WIDGET(viewer->doc_index_pane));
+       g_object_ref(GTK_WIDGET(viewer->doc_index_pane));
+       gtk_widget_show(GTK_WIDGET(viewer->scrollwin));
+       g_object_ref(GTK_WIDGET(viewer->scrollwin));
+       gtk_widget_show(GTK_WIDGET(viewer->icon_type_ebox));
+       g_object_ref(GTK_WIDGET(viewer->icon_type_ebox));
+       gtk_widget_show(GTK_WIDGET(viewer->pdf_view_ebox));
+       g_object_ref(GTK_WIDGET(viewer->pdf_view_ebox));
+       gtk_widget_show(GTK_WIDGET(viewer->scrollwin_index));
+       g_object_ref(GTK_WIDGET(viewer->scrollwin_index));
+       gtk_widget_show(GTK_WIDGET(viewer->hbox));
+       g_object_ref(GTK_WIDGET(viewer->hbox)); 
+       gtk_widget_show(GTK_WIDGET(viewer->vbox));
+       g_object_ref(GTK_WIDGET(viewer->vbox));
+
+       gtk_widget_show(GTK_WIDGET(viewer->widgets_table));
+       g_object_ref(GTK_WIDGET(viewer->widgets_table));
+
+       gtk_widget_show(GTK_WIDGET(viewer->cur_page));
+       g_object_ref(GTK_WIDGET(viewer->cur_page));
+
+       gtk_widget_show(GTK_WIDGET(viewer->first_page));
+       g_object_ref(GTK_WIDGET(viewer->first_page));
+
+       gtk_widget_show(GTK_WIDGET(viewer->last_page));
+       g_object_ref(GTK_WIDGET(viewer->last_page));
+
+       gtk_widget_show(GTK_WIDGET(viewer->prev_page));
+       g_object_ref(GTK_WIDGET(viewer->prev_page));
+
+       gtk_widget_show(GTK_WIDGET(viewer->next_page));
+       g_object_ref(GTK_WIDGET(viewer->next_page));
+
+       gtk_widget_show(GTK_WIDGET(viewer->zoom_in));
+       g_object_ref(GTK_WIDGET(viewer->zoom_in));
+       gtk_widget_show(GTK_WIDGET(viewer->zoom_out));
+       g_object_ref(GTK_WIDGET(viewer->zoom_out));
+       gtk_widget_show(GTK_WIDGET(viewer->zoom_fit));
+       g_object_ref(GTK_WIDGET(viewer->zoom_fit));
+       gtk_widget_show(GTK_WIDGET(viewer->zoom_width));
+       g_object_ref(GTK_WIDGET(viewer->zoom_width));
+
+       gtk_widget_show(GTK_WIDGET(viewer->rotate_right));
+       g_object_ref(GTK_WIDGET(viewer->rotate_right));
+       gtk_widget_show(GTK_WIDGET(viewer->rotate_left));
+       g_object_ref(GTK_WIDGET(viewer->rotate_left));
+       gtk_widget_show(GTK_WIDGET(viewer->doc_info));
+       g_object_ref(GTK_WIDGET(viewer->doc_info));
+       gtk_widget_show(GTK_WIDGET(viewer->doc_index));
+       g_object_ref(GTK_WIDGET(viewer->doc_index));
+
+       gtk_widget_show(GTK_WIDGET(viewer->doc_label));
+       g_object_ref(GTK_WIDGET(viewer->doc_label));
+       gtk_widget_show(GTK_WIDGET(viewer->icon_type));
+       g_object_ref(GTK_WIDGET(viewer->icon_type));    
+       gtk_widget_show(GTK_WIDGET(viewer->pdf_view));
+       g_object_ref(GTK_WIDGET(viewer->pdf_view));
+       gtk_widget_show(GTK_WIDGET(viewer->zoom_scroll));
+       g_object_ref(GTK_WIDGET(viewer->zoom_scroll));
+
+       gtk_widget_show(GTK_WIDGET(viewer->index_list));
+       g_object_ref(GTK_WIDGET(viewer->index_list));
+
+       /* Set Tooltips*/
+       CLAWS_SET_TIP(viewer->first_page,
+                               _("First Page"));
+
+       CLAWS_SET_TIP(viewer->prev_page,
+                               _("Previous Page"));
+
+       CLAWS_SET_TIP(viewer->next_page,
+                               _("Next Page"));
+       
+       CLAWS_SET_TIP(viewer->last_page,
+                               _("Last Page"));
+
+       CLAWS_SET_TIP(viewer->zoom_in,
+                               _("Zoom In"));
+       CLAWS_SET_TIP(viewer->zoom_out,
+                               _("Zoom Out"));
+
+       CLAWS_SET_TIP(viewer->zoom_fit,
+                               _("Fit Page"));
+
+       CLAWS_SET_TIP(viewer->zoom_width,
+                               _("Fit Page Width"));
+
+       CLAWS_SET_TIP(viewer->rotate_left,
+                               _("Rotate Left"));
+
+       CLAWS_SET_TIP(viewer->rotate_right,
+                               _("Rotate Right"));
+
+       CLAWS_SET_TIP(viewer->doc_info,
+                               _("Document Info"));
+
+       CLAWS_SET_TIP(viewer->doc_index,
+                               _("Document Index"));
+       CLAWS_SET_TIP(viewer->cur_page,
+                               _("Page Number"));
+       CLAWS_SET_TIP(viewer->zoom_scroll,
+                               _("Zoom Factor"));
+       /* Connect Signals */
+       g_signal_connect(G_OBJECT(viewer->cur_page), 
+                                   "value-changed", 
+                                   G_CALLBACK(pdf_viewer_spin_change_page_cb), 
+                                  (gpointer) viewer);
+
+       g_signal_connect(G_OBJECT(viewer->first_page), 
+                                   "clicked", 
+                                   G_CALLBACK(pdf_viewer_button_first_page_cb), 
+                                  (gpointer) viewer);
+       g_signal_connect(G_OBJECT(viewer->prev_page), 
+                                   "clicked", 
+                                   G_CALLBACK(pdf_viewer_button_prev_page_cb), 
+                                  (gpointer) viewer);
+       g_signal_connect(G_OBJECT(viewer->next_page), 
+                                   "clicked", 
+                                   G_CALLBACK(pdf_viewer_button_next_page_cb), 
+                                  (gpointer) viewer);
+       g_signal_connect(G_OBJECT(viewer->last_page), 
+                                   "clicked", 
+                                   G_CALLBACK(pdf_viewer_button_last_page_cb), 
+                                  (gpointer) viewer);
+       g_signal_connect(G_OBJECT(viewer->zoom_in), 
+                                   "clicked", 
+                                   G_CALLBACK(pdf_viewer_button_zoom_in_cb), 
+                                  (gpointer) viewer);
+       g_signal_connect(G_OBJECT(viewer->zoom_out), 
+                                   "clicked", 
+                                   G_CALLBACK(pdf_viewer_button_zoom_out_cb), 
+                                  (gpointer) viewer);
+       g_signal_connect(G_OBJECT(viewer->zoom_scroll), 
+                                   "value-changed", 
+                                   G_CALLBACK(pdf_viewer_spin_zoom_scroll_cb), 
+                                  (gpointer) viewer);
+
+       g_signal_connect(G_OBJECT(viewer->zoom_fit), 
+                                  "clicked", 
+                                   G_CALLBACK(pdf_viewer_button_zoom_fit_cb), 
+                                  (gpointer) viewer);
+
+       g_signal_connect(G_OBJECT(viewer->zoom_width), 
+                                   "clicked", 
+                                   G_CALLBACK(pdf_viewer_button_zoom_width_cb), 
+                                  (gpointer) viewer);
+
+       g_signal_connect(G_OBJECT(viewer->rotate_right), 
+                                   "clicked", 
+                                   G_CALLBACK(pdf_viewer_button_rotate_right_cb), 
+                                  (gpointer) viewer);
+       
+       g_signal_connect(G_OBJECT(viewer->rotate_left), 
+                                   "clicked", 
+                                   G_CALLBACK(pdf_viewer_button_rotate_left_cb), 
+                                  (gpointer) viewer);
+       
+       g_signal_connect(G_OBJECT(viewer->doc_info), 
+                                   "clicked", 
+                                   G_CALLBACK(pdf_viewer_button_document_info_cb), 
+                                  (gpointer) viewer);  
+       
+       g_signal_connect(G_OBJECT(viewer->doc_index), 
+                                   "clicked", 
+                                   G_CALLBACK(pdf_viewer_show_document_index_cb), 
+                                  (gpointer) viewer);
+       g_signal_connect(G_OBJECT(viewer->scrollwin), 
+                                   "scroll-event", 
+                                   G_CALLBACK(pdf_viewer_scroll_cb), 
+                                  (gpointer) viewer);
+       g_signal_connect(G_OBJECT(viewer->pdf_view_ebox), 
+                                   "button_press_event", 
+                                   G_CALLBACK(pdf_viewer_button_press_events_cb), 
+                                  (gpointer) viewer);
+       g_signal_connect(G_OBJECT(viewer->pdf_view_ebox), 
+                                   "button_release_event", 
+                                   G_CALLBACK(pdf_viewer_mouse_scroll_destroy_cb), 
+                                  (gpointer) viewer);
+       g_signal_connect(G_OBJECT(viewer->pdf_view_ebox), 
+                                   "motion_notify_event", 
+                                   G_CALLBACK(pdf_viewer_move_events_cb), 
+                                  (gpointer) viewer);
+
+       viewer->target_filename = NULL;
+       viewer->filename = NULL;
+       viewer->fsname = NULL;
+
+       return(MimeViewer *) viewer;
+}
+
+#undef ADD_BUTTON_TO_TABLE
+#undef ADD_SEP_TO_TABLE
+#undef BUTTON_H_PADDING
+#undef SEP_H_PADDING
+               
+static MimeViewerFactory pdf_viewer_factory =
+{
+       content_types,
+       0,
+       pdf_viewer_create,
+};
+
+gint plugin_init(gchar **error)
+{
+       gchar *gspath = NULL;
+
+       msg = g_strdup_printf(_("This plugin enables the viewing of PDF and PostScript "
+                               "attachments using the Poppler %s Lib and the gs tool.\n\n"
+                               "Any feedback is welcome: iwkse@claws-mail.org"
+                               ), poppler_get_version());
+
+       if (!check_plugin_version(MAKE_NUMERIC_VERSION(3,8,1,46),
+                   VERSION_NUMERIC, _("PDF Viewer"), error)) return -1;
+
+       if ((gspath = g_find_program_in_path("gs")) == NULL) {
+               gchar *pmsg = msg;
+               msg = g_strdup_printf(_("Warning: could not find ghostscript binary (gs) required "
+                                       "for %s plugin to process PostScript attachments, only PDF "
+                                       "attachments will be displayed. To enable PostScript "
+                                       "support please install gs program.\n\n%s"
+                                       ), _("PDF Viewer"), pmsg);
+               g_free(pmsg);
+       }
+       else {
+               g_free(gspath);
+       }
+       
+       mimeview_register_viewer_factory(&pdf_viewer_factory);
+       return 0;
+}
+
+gboolean plugin_done(void)
+{
+       g_free(msg);    
+       mimeview_unregister_viewer_factory(&pdf_viewer_factory);
+       return TRUE;
+}
+
+const gchar *plugin_name(void)
+{
+       return _("PDF Viewer");
+}
+
+const gchar *plugin_desc(void)
+{
+       return msg;
+}
+
+const gchar *plugin_type(void)
+{
+       return "GTK2";
+}
+
+const gchar *plugin_licence(void)
+{
+       return "GPL3+";
+}
+
+const gchar *plugin_version(void)
+{
+       return VERSION;
+}
+
+struct PluginFeature *plugin_provides(void)
+{
+       static struct PluginFeature features[] = 
+               { {PLUGIN_MIMEVIEWER, "application/pdf"},
+                 {PLUGIN_MIMEVIEWER, "application/postscript"},
+                 {PLUGIN_NOTHING, NULL} };
+       return features;
+}
+
diff --git a/src/plugins/pdf_viewer/poppler_viewer.h b/src/plugins/pdf_viewer/poppler_viewer.h
new file mode 100644 (file)
index 0000000..457b31c
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2007 the Claws Mail Team
+ * This file Copyright (C) 2007 Salvatore De Paolis 
+ * <iwkse@claws-mail.org> 
+ *
+ * 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#ifndef POPPLER_VIEWER_H
+#define POPPLER_VIEWER_H
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+//#include <unistd.h>
+#include <stdio.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <gdk/gdkkeysyms.h>
+#include <poppler.h>
+#include <version.h>
+#include <plugin.h>
+#include <messageview.h>
+#include <mimeview.h>
+#include <alertpanel.h>
+#include <mimeview.h>
+
+/*#ifdef USE_PTHREAD
+ *#include <pthread.h>
+ * #endif*/
+
+#define ZOOM_FACTOR 0.25
+#define ROTATION 90 
+#define ALPHA_CHANNEL 60
+#define SELECTION_COLOR 0xFF00FF
+
+static gchar *msg = NULL;
+
+struct _PageResult
+{
+       GList *results;
+       gint page_num;
+};
+
+typedef struct _PageResult PageResult;
+
+struct _PdfViewer
+{
+       MimeViewer                      mimeviewer;
+       GtkWidget                       *widgets_table;
+       GtkWidget                       *vbox;
+       GtkWidget                       *hbox;
+       GtkWidget                       *frame_index;
+       GtkWidget                       *pdf_view;
+       GtkWidget                       *scrollwin;
+       GtkWidget                       *scrollwin_index;
+       GtkWidget                       *pdf_view_ebox;
+       GtkWidget                       *icon_type_ebox;
+       GtkWidget                       *icon_type;
+       GdkPixbuf                       *icon_pixbuf;
+       GtkWidget                       *doc_label;
+       GtkWidget                       *cur_page;
+       GtkWidget                       *doc_index_pane;
+       GtkWidget                       *index_list;
+       /* begin GtkButtons */
+       GtkWidget                       *first_page;
+       GtkWidget                       *last_page;
+       GtkWidget                       *prev_page;
+       GtkWidget                       *next_page;
+       GtkWidget                       *zoom_in;
+       GtkWidget                       *zoom_out;
+       GtkWidget                       *zoom_scroll;
+       GtkWidget                       *zoom_fit;
+       GtkWidget                       *zoom_width;
+       GtkWidget                       *rotate_left;
+       GtkWidget                       *rotate_right;
+       GtkWidget                       *doc_info;
+       GtkWidget                       *doc_index;
+       /* end GtkButtons */
+       GtkTable                        *table_doc_info;
+#if !GTK_CHECK_VERSION(2,12,0)
+       GtkTooltips                     *button_bar_tips;
+#endif
+       PopplerDocument         *pdf_doc;
+       PopplerPage                     *pdf_page;
+       PopplerIndexIter        *pdf_index;
+       PopplerRectangle        *last_rect;
+       PopplerAction           *link_action;
+       PageResult                      *last_page_result;
+       GtkAdjustment           *pdf_view_vadj;
+       GtkAdjustment           *pdf_view_hadj;
+       GtkTreeModel            *index_model;
+
+       GList                           *link_map;
+       GList                           *page_results;
+       GList                           *text_found; /* GList of PageResults */
+       gchar                           *last_search;
+       gint                             last_match;
+       gint                             num_matches;
+
+       gchar                           *target_filename;
+       gchar                           *filename;
+       gchar                           *fsname;
+       gchar                           *doc_info_text;
+
+       gint                            res_cnt;
+       gint                            rotate;
+       gint                            num_pages;
+       gdouble                         zoom;
+       gdouble                         width;
+       gdouble                         height;
+       gdouble                         last_x;
+       gdouble                         last_y;
+       gint                            last_dir_x;
+       gint                            last_dir_y;
+       gboolean                        pdf_view_scroll;
+       gboolean                        in_link;
+       MimeInfo                        *mimeinfo;
+       MimeInfo                        *to_load;
+       
+};
+static gchar *content_types[] =
+       {"application/pdf", 
+        "application/postscript", 
+        NULL};
+typedef enum {
+       TYPE_UNKNOWN,
+       TYPE_PDF,
+       TYPE_PS
+} FileType;
+
+enum {
+       INDEX_NAME,
+       INDEX_PAGE,
+       INDEX_TOP,
+       N_INDEX_COLUMNS
+};
+
+typedef struct _PdfViewer PdfViewer;
+
+#endif /* POPPLER_VIEWER_H */
diff --git a/src/plugins/pdf_viewer/right_arrow.xpm b/src/plugins/pdf_viewer/right_arrow.xpm
new file mode 100644 (file)
index 0000000..09f3552
--- /dev/null
@@ -0,0 +1,44 @@
+/* XPM */
+static char * right_arrow_xpm[] = {
+"16 16 25 1",
+"      c None",
+".     c #000000",
+"+     c #5B88B2",
+"@     c #9EB8D1",
+"#     c #5080AD",
+"$     c #B5C9DC",
+"%     c #AFC5DA",
+"&     c #B2C7DB",
+"*     c #B6CADD",
+"=     c #A4BDD5",
+"-     c #9CB7D1",
+";     c #080D11",
+">     c #9BB6D0",
+",     c #A0BAD3",
+"'     c #9AB5CF",
+")     c #97B3CE",
+"!     c #5F8BB4",
+"~     c #91B0CC",
+"{     c #95B2CE",
+"]     c #4C79A3",
+"^     c #49749C",
+"/     c #3F6588",
+"(     c #2A435B",
+"_     c #456F96",
+":     c #375978",
+"                ",
+"                ",
+"        .       ",
+"        ..      ",
+"        .+.     ",
+"  .......@#.    ",
+"  .$%&***=-#;   ",
+"  .>,-->',-)!.  ",
+"  .~@''>---,{]. ",
+"  .^////////(.  ",
+"  ._::::://(.   ",
+"  ......./(.    ",
+"        .(.     ",
+"        ..      ",
+"        .       ",
+"                "};
diff --git a/src/plugins/pdf_viewer/rotate_left.xpm b/src/plugins/pdf_viewer/rotate_left.xpm
new file mode 100644 (file)
index 0000000..75aee48
--- /dev/null
@@ -0,0 +1,44 @@
+/* XPM */
+static char * rotate_left_xpm[] = {
+"16 16 25 1",
+"      c None",
+".     c #000000",
+"+     c #5B88B2",
+"@     c #5080AD",
+"#     c #9EB8D1",
+"$     c #080D11",
+"%     c #9CB7D1",
+"&     c #A4BDD5",
+"*     c #B6CADD",
+"=     c #B2C7DB",
+"-     c #AFC5DA",
+";     c #B5C9DC",
+">     c #5F8BB4",
+",     c #97B3CE",
+"'     c #A0BAD3",
+")     c #9AB5CF",
+"!     c #9BB6D0",
+"~     c #4C79A3",
+"{     c #95B2CE",
+"]     c #91B0CC",
+"^     c #2A435B",
+"/     c #3F6588",
+"(     c #375978",
+"_     c #456F96",
+":     c #49749C",
+"      .         ",
+"     ..         ",
+"    .+.         ",
+"   .@#.......   ",
+"  $@%&***=-;;.  ",
+" .>,%')!%%'!!!. ",
+".~{'%%%!))#]]!;.",
+" .^/////////]!;.",
+"  .^//(((((/]!;.",
+"   .^/...((/]!;.",
+"    .^.  .(/!!*.",
+"     ..  .(/)%*.",
+"      .  .(/)%=.",
+"         .(/#'-.",
+"         ._:]!;.",
+"         ......."};
diff --git a/src/plugins/pdf_viewer/rotate_right.xpm b/src/plugins/pdf_viewer/rotate_right.xpm
new file mode 100644 (file)
index 0000000..7da2b11
--- /dev/null
@@ -0,0 +1,44 @@
+/* XPM */
+static char * rotate_right_xpm[] = {
+"16 16 25 1",
+"      c None",
+".     c #000000",
+"+     c #5B88B2",
+"@     c #9EB8D1",
+"#     c #5080AD",
+"$     c #B5C9DC",
+"%     c #AFC5DA",
+"&     c #B2C7DB",
+"*     c #B6CADD",
+"=     c #A4BDD5",
+"-     c #9CB7D1",
+";     c #080D11",
+">     c #9BB6D0",
+",     c #A0BAD3",
+"'     c #9AB5CF",
+")     c #97B3CE",
+"!     c #5F8BB4",
+"~     c #91B0CC",
+"{     c #95B2CE",
+"]     c #4C79A3",
+"^     c #3F6588",
+"/     c #2A435B",
+"(     c #375978",
+"_     c #49749C",
+":     c #456F96",
+"         .      ",
+"         ..     ",
+"         .+.    ",
+"   .......@#.   ",
+"  .$$%&***=-#;  ",
+" .>>>,-->',-)!. ",
+".$>~~@''>---,{].",
+".$>~^^^^^^^^^/. ",
+".$>~^(((((^^/.  ",
+".$>~^((...^/.   ",
+".*>>^(.  ./.    ",
+".*-'^(.  ..     ",
+".&-'^(.  .      ",
+".%,@^(.         ",
+".$>~_:.         ",
+".......         "};
diff --git a/src/plugins/pdf_viewer/zoom_fit.xpm b/src/plugins/pdf_viewer/zoom_fit.xpm
new file mode 100644 (file)
index 0000000..edd29a7
--- /dev/null
@@ -0,0 +1,66 @@
+/* XPM */
+static char * zoom_fit_xpm[] = {
+"14 14 49 1",
+"      c None",
+".     c #46463E",
+"+     c #090908",
+"@     c #4A4A42",
+"#     c #33332D",
+"$     c #B6B6AB",
+"%     c #E2E2D6",
+"&     c #DDDDD0",
+"*     c #ABABA0",
+"=     c #34342F",
+"-     c #45453E",
+";     c #B6B6AA",
+">     c #10101A",
+",     c #0B0B0F",
+"'     c #DBDCD0",
+")     c #2B2B35",
+"!     c #A3A398",
+"~     c #4B4B43",
+"{     c #080807",
+"]     c #E6E6D9",
+"^     c #030303",
+"/     c #DEDED2",
+"(     c #D6D6CC",
+"_     c #CECEC4",
+":     c #31313B",
+"<     c #C0C0B8",
+"[     c #080808",
+"}     c #E0E0D4",
+"|     c #ECECE6",
+"1     c #D7D7CD",
+"2     c #D0D0C6",
+"3     c #C7C7BF",
+"4     c #AEAEA4",
+"5     c #262630",
+"6     c #D2D2C8",
+"7     c #CACAC0",
+"8     c #C2C2BA",
+"9     c #3D3D45",
+"0     c #BCBCB4",
+"a     c #2D2D37",
+"b     c #97978D",
+"c     c #4B4B44",
+"d     c #33332E",
+"e     c #A6A69C",
+"f     c #C7C7BE",
+"g     c #BFBFB7",
+"h     c #98988E",
+"i     c #000000",
+"j     c #404040",
+"  .+++@       ",
+" #$%&**=      ",
+"-;>,'))!~     ",
+"{]^/(_:<+     ",
+"[}|1233<+     ",
+"+4567890+     ",
+"-45a799bc     ",
+" def<ghii     ",
+"  @+++jiii    ",
+"        iii   ",
+"         iii  ",
+"          iii ",
+"           ii ",
+"              "};
diff --git a/src/plugins/pdf_viewer/zoom_in.xpm b/src/plugins/pdf_viewer/zoom_in.xpm
new file mode 100644 (file)
index 0000000..0ca7c80
--- /dev/null
@@ -0,0 +1,63 @@
+/* XPM */
+static char * zoom_in_xpm[] = {
+"14 14 46 1",
+"      c None",
+".     c #46463E",
+"+     c #090908",
+"@     c #4A4A42",
+"#     c #33332D",
+"$     c #B6B6AB",
+"%     c #E2E2D6",
+"&     c #DDDDD0",
+"*     c #ABABA0",
+"=     c #34342F",
+"-     c #45453E",
+";     c #B6B6AA",
+">     c #EFEFE5",
+",     c #F4F4F0",
+"'     c #24232F",
+")     c #D4D4CA",
+"!     c #A3A398",
+"~     c #4B4B43",
+"{     c #080807",
+"]     c #E6E6D9",
+"^     c #FCFCFC",
+"/     c #DEDED2",
+"(     c #292933",
+"_     c #CECEC4",
+":     c #C0C0B8",
+"<     c #080808",
+"[     c #E0E0D4",
+"}     c #131319",
+"|     c #282832",
+"1     c #2F2F39",
+"2     c #383840",
+"3     c #AEAEA4",
+"4     c #D9D9CF",
+"5     c #D2D2C8",
+"6     c #35353F",
+"7     c #C2C2BA",
+"8     c #BCBCB4",
+"9     c #97978D",
+"0     c #4B4B44",
+"a     c #33332E",
+"b     c #A6A69C",
+"c     c #C7C7BE",
+"d     c #BFBFB7",
+"e     c #98988E",
+"f     c #000000",
+"g     c #404040",
+"  .+++@       ",
+" #$%&**=      ",
+"-;>,'))!~     ",
+"{]^/(__:+     ",
+"<[}|122:+     ",
+"+3456778+     ",
+"-34567790     ",
+" abc:deff     ",
+"  @+++gfff    ",
+"        fff   ",
+"         fff  ",
+"          fff ",
+"           ff ",
+"              "};
diff --git a/src/plugins/pdf_viewer/zoom_out.xpm b/src/plugins/pdf_viewer/zoom_out.xpm
new file mode 100644 (file)
index 0000000..8367cec
--- /dev/null
@@ -0,0 +1,63 @@
+/* XPM */
+static char * zoom_out_xpm[] = {
+"14 14 46 1",
+"      c None",
+".     c #46463E",
+"+     c #090908",
+"@     c #4A4A42",
+"#     c #33332D",
+"$     c #B6B6AB",
+"%     c #E2E2D6",
+"&     c #DDDDD0",
+"*     c #ABABA0",
+"=     c #34342F",
+"-     c #45453E",
+";     c #B6B6AA",
+">     c #EFEFE5",
+",     c #F4F4F0",
+"'     c #DBDCD0",
+")     c #D4D4CA",
+"!     c #A3A398",
+"~     c #4B4B43",
+"{     c #080807",
+"]     c #E6E6D9",
+"^     c #FCFCFC",
+"/     c #DEDED2",
+"(     c #D6D6CC",
+"_     c #CECEC4",
+":     c #C0C0B8",
+"<     c #080808",
+"[     c #E0E0D4",
+"}     c #131319",
+"|     c #282832",
+"1     c #2F2F39",
+"2     c #383840",
+"3     c #AEAEA4",
+"4     c #D9D9CF",
+"5     c #D2D2C8",
+"6     c #CACAC0",
+"7     c #C2C2BA",
+"8     c #BCBCB4",
+"9     c #97978D",
+"0     c #4B4B44",
+"a     c #33332E",
+"b     c #A6A69C",
+"c     c #C7C7BE",
+"d     c #BFBFB7",
+"e     c #98988E",
+"f     c #000000",
+"g     c #404040",
+"  .+++@       ",
+" #$%&**=      ",
+"-;>,'))!~     ",
+"{]^/(__:+     ",
+"<[}|122:+     ",
+"+3456778+     ",
+"-34567790     ",
+" abc:deff     ",
+"  @+++gfff    ",
+"        fff   ",
+"         fff  ",
+"          fff ",
+"           ff ",
+"              "};
diff --git a/src/plugins/pdf_viewer/zoom_width.xpm b/src/plugins/pdf_viewer/zoom_width.xpm
new file mode 100644 (file)
index 0000000..ff0e41c
--- /dev/null
@@ -0,0 +1,66 @@
+/* XPM */
+static char * zoom_width_xpm[] = {
+"14 14 49 1",
+"      c None",
+".     c #46463E",
+"+     c #090908",
+"@     c #4A4A42",
+"#     c #33332D",
+"$     c #B6B6AB",
+"%     c #E2E2D6",
+"&     c #DDDDD0",
+"*     c #ABABA0",
+"=     c #34342F",
+"-     c #45453E",
+";     c #B6B6AA",
+">     c #EFEFE5",
+",     c #F4F4F0",
+"'     c #DBDCD0",
+")     c #D4D4CA",
+"!     c #A3A398",
+"~     c #4B4B43",
+"{     c #080807",
+"]     c #E6E6D9",
+"^     c #FCFCFC",
+"/     c #21212D",
+"(     c #D6D6CC",
+"_     c #31313B",
+":     c #CECEC4",
+"<     c #C0C0B8",
+"[     c #080808",
+"}     c #E0E0D4",
+"|     c #131319",
+"1     c #282832",
+"2     c #2F2F39",
+"3     c #383840",
+"4     c #AEAEA4",
+"5     c #D9D9CF",
+"6     c #2D2D37",
+"7     c #CACAC0",
+"8     c #3D3D45",
+"9     c #C2C2BA",
+"0     c #BCBCB4",
+"a     c #D2D2C8",
+"b     c #97978D",
+"c     c #4B4B44",
+"d     c #33332E",
+"e     c #A6A69C",
+"f     c #C7C7BE",
+"g     c #BFBFB7",
+"h     c #98988E",
+"i     c #000000",
+"j     c #404040",
+"  .+++@       ",
+" #$%&**=      ",
+"-;>,'))!~     ",
+"{]^/(_:<+     ",
+"[}|1233<+     ",
+"+4567890+     ",
+"-45a799bc     ",
+" def<ghii     ",
+"  @+++jiii    ",
+"        iii   ",
+"         iii  ",
+"          iii ",
+"           ii ",
+"              "};
diff --git a/src/plugins/perl/Makefile.am b/src/plugins/perl/Makefile.am
new file mode 100644 (file)
index 0000000..c99bb4e
--- /dev/null
@@ -0,0 +1,25 @@
+plugindir = $(pkglibdir)/plugins
+
+plugin_LTLIBRARIES = perl_plugin.la
+
+perl_plugin_la_SOURCES = \
+       perl_plugin.c perl_plugin.h \
+       perl_gtk.c perl_gtk.h
+
+perl_plugin_la_LDFLAGS = \
+       -avoid-version -module
+
+perl_plugin_la_LIBADD = \
+       $(PERL_LDFLAGS) \
+       $(GTK_LIBS)
+
+AM_CPPFLAGS = \
+       $(CLAWS_MAIL_CFLAGS) \
+       $(GLIB_CFLAGS) \
+       $(GTK_CFLAGS) \
+       $(PERL_CFLAGS)
+
+INCLUDES = \
+       -I$(top_srcdir)/src \
+       -I$(top_srcdir)/src/common \
+       -I$(top_srcdir)/src/gtk
diff --git a/src/plugins/perl/perl_gtk.c b/src/plugins/perl/perl_gtk.c
new file mode 100644 (file)
index 0000000..f69feb9
--- /dev/null
@@ -0,0 +1,104 @@
+/* Perl plugin -- Perl Support for Claws Mail
+ *
+ * Copyright (C) 2004-2007 Holger Berndt
+ *
+ * Sylpheed and Claws-Mail are GTK+ based, lightweight, and fast e-mail clients
+ * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail Team
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "common/utils.h"
+#include "mainwindow.h"
+#include "prefs_common.h"
+#include "main.h"
+#include "menu.h"
+
+#include "perl_plugin.h"
+#include "perl_gtk.h"
+
+static void perl_filter_edit(GtkAction *,gpointer);
+
+
+static GtkActionEntry mainwindow_tools_perl_edit[] = {{
+       "Tools/EditPerlRules",
+       NULL, N_("Edit perl filter rules (ext)..."), NULL, NULL, G_CALLBACK(perl_filter_edit)
+}};
+
+static gint main_menu_id = 0;
+
+
+void perl_gtk_init(void)
+{
+  MainWindow *mainwin;
+
+  mainwin =  mainwindow_get_mainwindow();
+
+  gtk_action_group_add_actions(mainwin->action_group, mainwindow_tools_perl_edit,
+                 1, (gpointer)mainwin);
+  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "EditPerlRules", 
+                   "Tools/EditPerlRules", GTK_UI_MANAGER_MENUITEM,
+                   main_menu_id)
+}
+
+void perl_gtk_done(void)
+{
+  MainWindow *mainwin;
+
+  mainwin = mainwindow_get_mainwindow();
+
+  if(mainwin && !claws_is_exiting()) {
+    MENUITEM_REMUI_MANAGER(mainwin->ui_manager,mainwin->action_group, "Tools/EditPerlRules", main_menu_id);
+    main_menu_id = 0;
+  }
+}
+
+static void perl_filter_edit(GtkAction *action, gpointer callback_data)
+{
+  gchar *perlfilter;
+  gchar *pp;
+  gchar buf[1024];
+  gchar **cmdline;
+
+  perlfilter = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PERLFILTER, NULL);
+  if (prefs_common_get_ext_editor_cmd() &&
+      (pp = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
+      *(pp + 1) == 's' && !strchr(pp + 2, '%')) {
+    g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), perlfilter);
+  }
+  else {
+    if (prefs_common_get_ext_editor_cmd())
+      g_warning("Perl Plugin: External editor command-line is invalid: `%s'",
+               prefs_common_get_ext_editor_cmd());
+    g_snprintf(buf, sizeof(buf), "emacs %s", perlfilter);
+  }
+  g_free(perlfilter);
+  cmdline = strsplit_with_quote(buf, " ", 1024);
+  execute_detached(cmdline);
+  g_strfreev(cmdline);
+}
diff --git a/src/plugins/perl/perl_gtk.h b/src/plugins/perl/perl_gtk.h
new file mode 100644 (file)
index 0000000..75fae32
--- /dev/null
@@ -0,0 +1,28 @@
+/* Perl plugin -- Perl Support for Claws Mail
+ *
+ * Copyright (C) 2004-2007 Holger Berndt
+ *
+ * Sylpheed and Claws-Mail are GTK+ based, lightweight, and fast e-mail clients
+ * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail Team
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SC_PERL_GTK_H
+#define SC_PERL_GTK_H SC_PERL_GTK_H
+
+void perl_gtk_init(void);
+void perl_gtk_done(void);
+
+#endif
diff --git a/src/plugins/perl/perl_plugin.c b/src/plugins/perl/perl_plugin.c
new file mode 100644 (file)
index 0000000..0c34590
--- /dev/null
@@ -0,0 +1,2400 @@
+/* Perl plugin -- Perl Support for Claws Mail
+ *
+ * Copyright (C) 2004-2007 Holger Berndt
+ *
+ * Sylpheed and Claws-Mail are GTK+ based, lightweight, and fast e-mail clients
+ * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail Team
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include "common/version.h"
+#include "common/defs.h"
+#include "common/utils.h"
+#include "common/claws.h"
+#include "common/prefs.h"
+#include "procmsg.h"
+#include "procheader.h"
+#include "folder.h"
+#include "account.h"
+#include "compose.h"
+#include "addrindex.h"
+#include "addritem.h"
+#include "addr_compl.h"
+#include "statusbar.h"
+#include "alertpanel.h"
+#include "common/hooks.h"
+#include "prefs_common.h"
+#include "prefs_gtk.h"
+#include "common/log.h"
+#include "common/plugin.h"
+#include "common/tags.h"
+
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+
+#ifdef _
+# undef _
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "perl_plugin.h"
+#include "perl_gtk.h"
+
+
+/* XSRETURN_UV was introduced in Perl 5.8.1,
+   this fixes things for 5.8.0. */
+#ifndef XSRETURN_UV
+#  ifndef XST_mUV
+#    define XST_mUV(i,v)  (ST(i) = sv_2mortal(newSVuv(v))  )
+#  endif /* XST_mUV */
+#  define XSRETURN_UV(v) STMT_START { XST_mUV(0,v);  XSRETURN(1); } STMT_END
+#endif /* XSRETURN_UV */
+
+/* set this to "1" to recompile the Perl script for every mail,
+   even if it hasn't changed */
+#define DO_CLEAN "0"
+
+/* distinguish between automatic and manual filtering */
+#define AUTO_FILTER 0
+#define MANU_FILTER 1
+
+/* embedded Perl stuff */
+static PerlInterpreter *my_perl = NULL;
+EXTERN_C void xs_init(pTHX);
+EXTERN_C void boot_DynaLoader (pTHX_ CV* cv);
+
+/* plugin stuff */
+static guint             filtering_hook_id;
+static guint             manual_filtering_hook_id;
+static MailFilteringData *mail_filtering_data  = NULL;
+static MsgInfo           *msginfo              = NULL;
+static gboolean          stop_filtering        = FALSE;
+static gboolean          manual_filtering      = FALSE;
+static gboolean          wrote_filter_log_head = FALSE;
+static gint              filter_log_verbosity;
+static FILE              *message_file         = NULL;
+static gchar             *attribute_key        = NULL;
+
+/* configuration */
+static PerlPluginConfig config;
+
+static PrefParam param[] = {
+  {"filter_log_verbosity", "2", &config.filter_log_verbosity,
+   P_INT, NULL, NULL, NULL},
+  {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
+};
+
+
+/* Utility functions */
+
+/* fire and forget */
+gint execute_detached(gchar **cmdline)
+{
+  pid_t pid;
+  
+  if((pid = fork()) < 0) { /* fork error */
+    perror("fork");
+    return 0;
+  }
+  else if(pid > 0) {       /* parent */
+    waitpid(pid, NULL, 0);
+    return 1;
+  }
+  else {                   /* child */
+    if((pid = fork()) < 0) { /* fork error */
+      perror("fork");
+      return 0;
+    }
+    else if(pid > 0) {     /* child */
+      /* make grand child an orphan */
+      _exit(0);
+    }
+    else {                 /* grand child */
+      execvp(cmdline[0], cmdline);
+      perror("execvp");
+      _exit(1);
+    }
+  }
+  return 0;
+}
+
+
+/* filter logfile */
+#define LOG_MANUAL 1
+#define LOG_ACTION 2
+#define LOG_MATCH  3
+
+static void filter_log_write(gint type, gchar *text) {
+  if(filter_log_verbosity >= type) {
+    if(!wrote_filter_log_head) {
+      log_message(LOG_PROTOCOL, "From: %s || Subject: %s || Message-ID: %s\n",
+        msginfo->from    ? msginfo->from    : "<no From header>",
+        msginfo->subject ? msginfo->subject : "<no Subject header>",
+        msginfo->msgid   ? msginfo->msgid   : "<no message id>");
+      wrote_filter_log_head = TRUE;
+    }
+    switch(type) {
+    case LOG_MANUAL:
+      log_message(LOG_PROTOCOL, "    MANUAL: %s\n", text?text:"<no text specified>");
+      break;
+    case LOG_ACTION:
+      log_message(LOG_PROTOCOL, "    ACTION: %s\n", text?text:"<no text specified>");
+      break;
+    case LOG_MATCH:
+      log_message(LOG_PROTOCOL, "    MATCH:  %s\n", text?text:"<no text specified>");
+      break;
+    default:
+      g_warning("Perl Plugin: Wrong use of filter_log_write");
+      break;
+    }
+  }
+}
+
+/* Addressbook interface */
+
+static PerlPluginTimedSList *email_slist = NULL;
+static GHashTable *attribute_hash        = NULL;
+
+/* addressbook email collector callback */
+static gint add_to_email_slist(ItemPerson *person, const gchar *bookname)
+{
+  PerlPluginEmailEntry *ee;
+  GList *nodeM;
+
+  /* Process each E-Mail address */
+  nodeM = person->listEMail;
+  while(nodeM) {
+    ItemEMail *email = nodeM->data;
+    ee = g_new0(PerlPluginEmailEntry,1);
+    g_return_val_if_fail(ee != NULL, -1);
+
+    if(email->address != NULL) ee->address  = g_strdup(email->address);
+    else                       ee->address  = NULL;
+    if(bookname != NULL)       ee->bookname = g_strdup(bookname);
+    else                       ee->bookname = NULL;
+
+    email_slist->g_slist = g_slist_prepend(email_slist->g_slist,ee);
+    nodeM = g_list_next(nodeM);
+  }
+  return 0;
+}
+
+/* free a GSList of PerlPluginEmailEntry's. */
+static void free_PerlPluginEmailEntry_slist(GSList *slist)
+{
+  GSList *walk;
+
+  if(slist == NULL)
+    return;
+
+  walk = slist;
+  for(; walk != NULL; walk = g_slist_next(walk)) {
+    PerlPluginEmailEntry *ee = (PerlPluginEmailEntry *) walk->data;
+    if(ee != NULL) {
+      if(ee->address  != NULL) g_free(ee->address);
+      if(ee->bookname != NULL) g_free(ee->bookname);
+      g_free(ee);
+      ee = NULL;
+    }
+  }
+  g_slist_free(slist);
+
+  debug_print("PerlPluginEmailEntry slist freed\n");
+}
+
+/* free email_slist */
+static void free_email_slist(void)
+{
+  if(email_slist == NULL)
+    return;
+
+  free_PerlPluginEmailEntry_slist(email_slist->g_slist);
+  email_slist->g_slist = NULL;
+
+  g_free(email_slist);
+  email_slist = NULL;
+
+  debug_print("email_slist freed\n");
+}
+
+/* check if tl->g_slist exists and is recent enough */
+static gboolean update_PerlPluginTimedSList(PerlPluginTimedSList *tl)
+{
+  gboolean retVal;
+  gchar *indexfile;
+  struct stat filestat;
+
+  if(tl->g_slist == NULL)
+    return TRUE;
+
+  indexfile = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ADDRESSBOOK_INDEX_FILE, NULL);
+  if((stat(indexfile,&filestat) == 0) && filestat.st_mtime <= tl->mtime)
+     retVal = FALSE;
+  else
+    retVal = TRUE;
+
+  g_free(indexfile);
+  return retVal;
+}
+
+/* (re)initialize email slist */
+static void init_email_slist(void)
+{
+  gchar *indexfile;
+  struct stat filestat;
+
+  if(email_slist->g_slist != NULL) {
+    free_PerlPluginEmailEntry_slist(email_slist->g_slist);
+    email_slist->g_slist = NULL;
+  }
+
+  addrindex_load_person_attribute(NULL,add_to_email_slist);
+
+  indexfile = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ADDRESSBOOK_INDEX_FILE, NULL);
+  if(stat(indexfile,&filestat) == 0)
+    email_slist->mtime = filestat.st_mtime;
+  g_free(indexfile);
+  debug_print("Initialisation of email slist completed\n");
+}
+
+/* check if given address is in given addressbook */
+static gboolean addr_in_addressbook(gchar *addr, gchar *bookname)
+{
+  /* If no book is given, check the address completion list
+   * (there may be other addresses that are not in the address book,
+   * added by other plugins). */
+  if(bookname == NULL) {
+    gboolean found;
+    start_address_completion(NULL);
+    found = (complete_matches_found(addr) > 0);
+    end_address_completion();
+    return found;
+  }
+  else {
+    GSList *walk;
+
+    /* check if email_list exists */
+    if(email_slist == NULL) {
+      email_slist = g_new0(PerlPluginTimedSList,1);
+      email_slist->g_slist = NULL;
+      debug_print("email_slist created\n");
+    }
+
+    if(update_PerlPluginTimedSList(email_slist))
+      init_email_slist();
+
+    walk = email_slist->g_slist;
+    for(; walk != NULL; walk = g_slist_next(walk)) {
+      PerlPluginEmailEntry *ee = (PerlPluginEmailEntry *) walk->data;
+      gchar *a = g_utf8_casefold(ee->address, -1);
+      gchar *b = g_utf8_casefold(addr, -1);
+      if((!g_utf8_collate(a,b)) &&
+         ((bookname == NULL) || (!strcmp(ee->bookname,bookname)))) {
+        g_free(a);
+        g_free(b);
+        return TRUE;
+      }
+      g_free(a);
+      g_free(b);
+    }
+  }
+
+  return FALSE;
+}
+
+/* attribute hash collector callback */
+static gint add_to_attribute_hash(ItemPerson *person, const gchar *bookname)
+{
+  PerlPluginTimedSList *tl;
+  PerlPluginAttributeEntry *ae;
+  GList *nodeA;
+  GList *nodeM;
+
+  nodeA = person->listAttrib;
+  /* Process each User Attribute */
+  while(nodeA) {
+    UserAttribute *attrib = nodeA->data;
+    if(attrib->name && !strcmp(attrib->name,attribute_key) ) {
+      /* Process each E-Mail address */
+      nodeM = person->listEMail;
+      while(nodeM) {
+  ItemEMail *email = nodeM->data;
+
+  ae = g_new0(PerlPluginAttributeEntry,1);
+  g_return_val_if_fail(ae != NULL, -1);
+
+  if(email->address != NULL) ae->address  = g_strdup(email->address);
+  else                       ae->address  = NULL;
+  if(attrib->value  != NULL) ae->value    = g_strdup(attrib->value);
+  else                       ae->value    = NULL;
+  if(bookname != NULL)       ae->bookname = g_strdup(bookname);
+  else                       ae->bookname = NULL;
+
+  tl = (PerlPluginTimedSList *) g_hash_table_lookup(attribute_hash,attribute_key);
+  tl->g_slist = g_slist_prepend(tl->g_slist,ae);
+
+  nodeM = g_list_next(nodeM);
+      }
+    }
+    nodeA = g_list_next(nodeA);
+  }
+  
+  return 0;
+}
+
+/* free a key of the attribute hash */
+static gboolean free_attribute_hash_key(gpointer key, gpointer value, gpointer user_data)
+{
+  GSList *walk;
+  PerlPluginTimedSList *tl;
+
+  debug_print("Freeing key `%s' from attribute_hash\n",key?(char*)key:"");
+
+  tl = (PerlPluginTimedSList *) value;
+
+  if(tl != NULL) {
+    if(tl->g_slist != NULL) {
+      walk = tl->g_slist;
+      for(; walk != NULL; walk = g_slist_next(walk)) {
+  PerlPluginAttributeEntry *ae = (PerlPluginAttributeEntry *) walk->data;
+  if(ae != NULL) {
+    if(ae->address  != NULL) g_free(ae->address);
+    if(ae->value    != NULL) g_free(ae->value);
+    if(ae->bookname != NULL) g_free(ae->bookname);
+    g_free(ae);
+    ae = NULL;
+  }
+      }
+      g_slist_free(tl->g_slist);
+      tl->g_slist = NULL;
+    }
+    g_free(tl);
+    tl = NULL;
+  }
+
+  if(key != NULL) {
+    g_free(key);
+    key = NULL;
+  }
+
+  return TRUE;
+}
+
+/* free whole attribute hash */
+static void free_attribute_hash(void)
+{
+  if(attribute_hash == NULL)
+    return;
+
+  g_hash_table_foreach_remove(attribute_hash,free_attribute_hash_key,NULL);
+  g_hash_table_destroy(attribute_hash);
+  attribute_hash = NULL;
+
+  debug_print("attribute_hash freed\n");
+}
+
+/* Free the key if it exists. Insert the new key. */
+static void insert_attribute_hash(gchar *attr)
+{
+  PerlPluginTimedSList *tl;
+  gchar *indexfile;
+  struct stat filestat;
+
+  /* Check if key exists. Free it if it does. */
+  if((tl = g_hash_table_lookup(attribute_hash,attr)) != NULL) {
+    gpointer origkey;
+    gpointer value;
+    g_hash_table_lookup_extended(attribute_hash,attr,&origkey,&value);
+    g_hash_table_remove(attribute_hash,origkey);
+    free_attribute_hash_key(origkey,value,NULL);
+    debug_print("Existing key `%s' freed.\n",attr);
+  }
+
+  tl = g_new0(PerlPluginTimedSList,1);
+  tl->g_slist = NULL;
+
+  attribute_key = g_strdup(attr);
+  g_hash_table_insert(attribute_hash,attribute_key,tl);  
+
+  addrindex_load_person_attribute(attribute_key,add_to_attribute_hash);
+
+  indexfile = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ADDRESSBOOK_INDEX_FILE, NULL);
+  if(stat(indexfile,&filestat) == 0)
+    tl->mtime = filestat.st_mtime;
+  g_free(indexfile);
+
+  debug_print("added key `%s' to attribute_hash\n",attribute_key?attribute_key:"");
+}
+
+/* check if an update of the attribute hash entry is necessary */
+static gboolean update_attribute_hash(const gchar *attr)
+{
+  PerlPluginTimedSList *tl;
+
+  /* check if key attr exists in the attribute hash */
+  if((tl = (PerlPluginTimedSList*) g_hash_table_lookup(attribute_hash,attr)) == NULL)
+    return TRUE;
+
+  /* check if entry is recent enough */
+  return update_PerlPluginTimedSList(tl);
+}
+
+/* given an email address, return attribute value of specific book */
+static gchar* get_attribute_value(gchar *email, gchar *attr, gchar *bookname)
+{
+  GSList *walk;
+  PerlPluginTimedSList *tl;
+
+  /* check if attribute hash exists */
+  if(attribute_hash == NULL) {
+    attribute_hash = g_hash_table_new(g_str_hash,g_str_equal);
+    debug_print("attribute_hash created\n");
+  }
+
+  if(update_attribute_hash(attr)) {
+    debug_print("Initialisation of attribute hash entry `%s' is necessary\n",attr);
+    insert_attribute_hash(attr);
+  }
+  
+  if((tl = (PerlPluginTimedSList*) g_hash_table_lookup(attribute_hash,attr)) == NULL)
+    return NULL;  
+
+  walk = tl->g_slist;
+  for(; walk != NULL; walk = g_slist_next(walk)) {
+    PerlPluginAttributeEntry *ae = (PerlPluginAttributeEntry *) walk->data;
+    gchar *a, *b;
+    a = g_utf8_strdown(ae->address, -1);
+    b = g_utf8_strdown(email, -1);
+    if(!g_utf8_collate(a, b)) {
+      if((bookname == NULL) ||
+   ((ae->bookname != NULL) && !strcmp(bookname,ae->bookname))) {
+        g_free(a); g_free(b);
+  return ae->value;
+      }
+    }
+    g_free(a); g_free(b);
+  }
+  return NULL;
+}
+
+/* free up all memory allocated with lists */
+static void free_all_lists(void)
+{
+  /* email list */
+  free_email_slist();
+
+  /* attribute hash */
+  free_attribute_hash();
+}
+
+
+
+/* ClawsMail::C module */
+
+/* Initialization */
+
+/* ClawsMail::C::filter_init(int) */
+static XS(XS_ClawsMail_filter_init)
+{
+  int flag;
+  /* flags:
+   *
+   *    msginfo
+   *          1 size
+   *          2 date
+   *          3 from
+   *          4 to
+   *          5 cc
+   *          6 newsgroups
+   *          7 subject
+   *          8 msgid
+   *          9 inreplyto
+   *         10 xref
+   *         11 xface
+   *         12 dispositionnotificationto
+   *         13 returnreceiptto
+   *         14 references
+   *         15 score
+   *         16 not used anymore
+   *         17 plaintext_file
+   *         18 not used anymore
+   *         19 hidden
+   *         20 message file path
+   *         21 partial_recv
+   *         22 total_size
+   *         23 account_server
+   *         24 account_login
+   *         25 planned_download
+   *
+   *    general
+   *        100 manual
+   */
+  char *charp;
+  gchar buf[BUFFSIZE];
+  GSList *walk;
+  int ii;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::init");
+    XSRETURN_UNDEF;
+  }
+  flag = SvIV(ST(0));
+  switch(flag) {
+
+    /* msginfo */
+  case  1:
+    msginfo->size       ? XSRETURN_UV(msginfo->size)       : XSRETURN_UNDEF;
+  case  2:
+    msginfo->date       ? XSRETURN_PV(msginfo->date)       : XSRETURN_UNDEF;
+  case  3:
+    msginfo->from       ? XSRETURN_PV(msginfo->from)       : XSRETURN_UNDEF;
+  case  4:
+    msginfo->to         ? XSRETURN_PV(msginfo->to)         : XSRETURN_UNDEF;
+  case  5:
+    msginfo->cc         ? XSRETURN_PV(msginfo->cc)         : XSRETURN_UNDEF;
+  case  6:
+    msginfo->newsgroups ? XSRETURN_PV(msginfo->newsgroups) : XSRETURN_UNDEF;
+  case  7:
+    msginfo->subject    ? XSRETURN_PV(msginfo->subject)    : XSRETURN_UNDEF;
+  case  8:
+    msginfo->msgid      ? XSRETURN_PV(msginfo->msgid)      : XSRETURN_UNDEF;
+  case  9:
+    msginfo->inreplyto  ? XSRETURN_PV(msginfo->inreplyto)  : XSRETURN_UNDEF;
+  case 10:
+    msginfo->xref       ? XSRETURN_PV(msginfo->xref)       : XSRETURN_UNDEF;
+  case 11:
+    (msginfo->extradata && msginfo->extradata->xface) ?
+      XSRETURN_PV(msginfo->extradata->xface)               : XSRETURN_UNDEF;
+  case 12:
+    (msginfo->extradata && msginfo->extradata->dispositionnotificationto) ?
+      XSRETURN_PV(msginfo->extradata->dispositionnotificationto) : XSRETURN_UNDEF;
+  case 13:
+    (msginfo->extradata && msginfo->extradata->returnreceiptto) ?
+      XSRETURN_PV(msginfo->extradata->returnreceiptto)     : XSRETURN_UNDEF;
+  case 14:
+    EXTEND(SP, g_slist_length(msginfo->references));
+    ii = 0;
+    for(walk = msginfo->references; walk != NULL; walk = g_slist_next(walk))
+      XST_mPV(ii++,walk->data ? (gchar*) walk->data: "");
+    ii ? XSRETURN(ii) : XSRETURN_UNDEF;
+  case 15:
+    msginfo->score      ? XSRETURN_IV(msginfo->score)      : XSRETURN_UNDEF;
+  case 17:
+    msginfo->plaintext_file ?
+      XSRETURN_PV(msginfo->plaintext_file)                 : XSRETURN_UNDEF;
+  case 19:
+    msginfo->hidden     ? XSRETURN_IV(msginfo->hidden)     : XSRETURN_UNDEF;
+  case 20:
+    if((charp = procmsg_get_message_file_path(msginfo)) != NULL) {
+      strncpy2(buf,charp,sizeof(buf));
+      g_free(charp);
+      XSRETURN_PV(buf);
+    }
+    else
+      XSRETURN_UNDEF;
+  case 21:
+    (msginfo->extradata && msginfo->extradata->partial_recv) ?
+      XSRETURN_PV(msginfo->extradata->partial_recv)        : XSRETURN_UNDEF;
+  case 22:
+    msginfo->total_size ? XSRETURN_IV(msginfo->total_size) : XSRETURN_UNDEF;
+  case 23:
+    (msginfo->extradata && msginfo->extradata->account_server) ?
+      XSRETURN_PV(msginfo->extradata->account_server)      : XSRETURN_UNDEF;
+  case 24:
+    (msginfo->extradata && msginfo->extradata->account_login) ?
+      XSRETURN_PV(msginfo->extradata->account_login)       : XSRETURN_UNDEF;
+  case 25:
+    msginfo->planned_download ?
+      XSRETURN_IV(msginfo->planned_download)               : XSRETURN_UNDEF;
+
+    /* general */
+  case 100:
+    if(manual_filtering)
+      XSRETURN_YES;
+    else
+      XSRETURN_NO;
+  default:
+    g_warning("Perl Plugin: Wrong argument to ClawsMail::C::init");
+    XSRETURN_UNDEF;    
+  }
+}
+
+/* ClawsMail::C::open_mail_file */
+static XS(XS_ClawsMail_open_mail_file)
+{
+  char *file;
+  gchar buf[BUFFSIZE];
+
+  dXSARGS;
+  if(items != 0) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::open_mail_file");
+    XSRETURN_UNDEF;
+  }
+  file = procmsg_get_message_file_path(msginfo);
+  if(!file)
+    XSRETURN_UNDEF;
+  strncpy2(buf,file,sizeof(buf));
+  g_free(file);
+  if((message_file = fopen(buf, "rb")) == NULL) {
+    FILE_OP_ERROR(buf, "fopen");
+    g_warning("Perl Plugin: File open error in ClawsMail::C::open_mail_file");
+    XSRETURN_UNDEF;
+  }
+}
+
+/* ClawsMail::C::close_mail_file */
+static XS(XS_ClawsMail_close_mail_file)
+{
+  dXSARGS;
+  if(items != 0) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::close_mail_file");
+    XSRETURN_UNDEF;
+  }
+  if(message_file != NULL)
+    fclose(message_file);
+  XSRETURN_YES;
+}
+
+/* ClawsMail::C::get_next_header */
+static XS(XS_ClawsMail_get_next_header)
+{
+  gchar buf[BUFFSIZE];
+  Header *header;
+
+  dXSARGS;
+  if(items != 0) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::get_next_header");
+    XSRETURN_EMPTY;
+  }
+  if(message_file == NULL) {
+    g_warning("Perl Plugin: Message file not open. Use ClawsMail::C::open_message_file first.");
+    XSRETURN_EMPTY;
+  }
+  if(procheader_get_one_field(buf, sizeof(buf), message_file, NULL) != -1) {
+    header = procheader_parse_header(buf);
+    EXTEND(SP, 2);
+    if(header) {
+      XST_mPV(0,header->name);
+      XST_mPV(1,header->body);
+      procheader_header_free(header);
+    }
+    else {
+      XST_mPV(0,"");
+      XST_mPV(1,"");
+    }
+    XSRETURN(2);
+  }
+  else
+    XSRETURN_EMPTY;
+}
+
+/* ClawsMail::C::get_next_body_line */
+static XS(XS_ClawsMail_get_next_body_line)
+{
+  gchar buf[BUFFSIZE];
+
+  dXSARGS;
+  if(items != 0) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::get_next_body_line");
+    XSRETURN_UNDEF;
+  }
+  if(message_file == NULL) {
+    g_warning("Perl Plugin: Message file not open. Use ClawsMail::C::open_message_file first.");
+    XSRETURN_UNDEF;
+  }
+  if(fgets(buf, sizeof(buf), message_file) != NULL)
+    XSRETURN_PV(buf);
+  else
+    XSRETURN_UNDEF;
+}
+
+
+/* Filter matchers */
+
+/* ClawsMail::C::check_flag(int) */
+static XS(XS_ClawsMail_check_flag)
+{
+  int flag;
+  /* flags:  1 marked
+   *         2 unread
+   *         3 deleted
+   *       4 new
+   *       5 replied
+   *       6 forwarded
+   *       7 locked
+   *         8 ignore thread
+   */
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::check_flag");
+    XSRETURN_UNDEF;
+  }
+  flag = SvIV(ST(0));
+
+  switch(flag) {
+  case 1:
+    if(MSG_IS_MARKED(msginfo->flags)) {
+      filter_log_write(LOG_MATCH,"marked");
+      XSRETURN_YES;
+    }
+    else
+      XSRETURN_NO;
+  case 2:
+    if(MSG_IS_UNREAD(msginfo->flags)) {
+      filter_log_write(LOG_MATCH,"unread");
+      XSRETURN_YES;
+    }
+    else
+      XSRETURN_NO;
+  case 3:
+    if(MSG_IS_DELETED(msginfo->flags)) {
+      filter_log_write(LOG_MATCH,"deleted");
+      XSRETURN_YES;
+    }
+    else
+      XSRETURN_NO;
+  case 4:
+    if(MSG_IS_NEW(msginfo->flags)) {
+      filter_log_write(LOG_MATCH,"new");
+      XSRETURN_YES;
+    }
+    else
+      XSRETURN_NO;
+  case 5:
+    if(MSG_IS_REPLIED(msginfo->flags)) {
+      filter_log_write(LOG_MATCH,"replied");
+      XSRETURN_YES;
+    }
+    else
+      XSRETURN_NO;
+  case 6:
+    if(MSG_IS_FORWARDED(msginfo->flags)) {
+      filter_log_write(LOG_MATCH,"forwarded");
+      XSRETURN_YES;
+    }
+    else
+      XSRETURN_NO;
+  case 7:
+    if(MSG_IS_LOCKED(msginfo->flags)) {
+      filter_log_write(LOG_MATCH,"locked");
+      XSRETURN_YES;
+    }
+    else
+      XSRETURN_NO;
+  case 8:
+    if(MSG_IS_IGNORE_THREAD(msginfo->flags)) {
+      filter_log_write(LOG_MATCH,"ignore_thread");
+      XSRETURN_YES;
+    }
+    else
+      XSRETURN_NO;
+  default:
+    g_warning("Perl Plugin: Unknown argument to ClawsMail::C::check_flag");
+    XSRETURN_UNDEF;
+  }
+}
+
+/* ClawsMail::C::colorlabel(int) */
+static XS(XS_ClawsMail_colorlabel)
+{
+  int color;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::colorlabel");
+    XSRETURN_UNDEF;
+  }
+  color = SvIV(ST(0));
+
+  if((MSG_GET_COLORLABEL_VALUE(msginfo->flags) == (guint32)color)) {
+    filter_log_write(LOG_MATCH,"colorlabel");
+    XSRETURN_YES;
+  }
+  else
+    XSRETURN_NO;
+}
+
+/* ClawsMail::C::age_greater(int) */
+static XS(XS_ClawsMail_age_greater)
+{
+  int age;
+  time_t t;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::age_greater");
+    XSRETURN_UNDEF;
+  }
+  age = SvIV(ST(0));
+  t = time(NULL);
+  if(((t - msginfo->date_t) / 86400) >= age) {
+    filter_log_write(LOG_MATCH,"age_greater");
+    XSRETURN_YES;
+  }
+  else
+    XSRETURN_NO;
+}
+
+/* ClawsMail::C::age_lower(int) */
+static XS(XS_ClawsMail_age_lower)
+{
+  int age;
+  time_t t;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::age_lower");
+    XSRETURN_UNDEF;
+  }
+  age = SvIV(ST(0));
+  t = time(NULL);
+  if(((t - msginfo->date_t) / 86400) <= age) {
+    filter_log_write(LOG_MATCH,"age_lower");
+    XSRETURN_YES;
+  }
+  else
+    XSRETURN_NO;
+}
+
+/* ClawsMail::C::tagged() */
+static XS(XS_ClawsMail_tagged)
+{
+  dXSARGS;
+  if(items != 0) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::tagged");
+    XSRETURN_UNDEF;
+  }
+
+  return msginfo->tags ? XSRETURN_YES : XSRETURN_NO;
+}
+
+/* ClawsMail::C::get_tags() */
+static XS(XS_ClawsMail_get_tags)
+{
+  guint iTag;
+  guint num_tags;
+  GSList *walk;
+
+  dXSARGS;
+  if(items != 0) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::get_tags");
+    XSRETURN_UNDEF;
+  }
+
+  num_tags = g_slist_length(msginfo->tags);
+
+  EXTEND(SP, num_tags);
+  iTag = 0;
+  for(walk = msginfo->tags; walk != NULL; walk = g_slist_next(walk)) {
+    const char *tag_str;
+    tag_str = tags_get_tag(GPOINTER_TO_INT(walk->data));
+    XST_mPV(iTag++, tag_str ? tag_str: "");
+  }
+
+  XSRETURN(num_tags);
+}
+
+
+
+/* ClawsMail::C::set_tag(char*) */
+static XS(XS_ClawsMail_set_tag)
+{
+  gchar *tag_str;
+  gint tag_id;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::set_tag");
+    XSRETURN_UNDEF;
+  }
+
+  tag_str = SvPV_nolen(ST(0));
+  tag_id = tags_get_id_for_str(tag_str);
+  if(tag_id == -1) {
+    g_warning("Perl Plugin: set_tag requested setting of a non-existing tag");
+    XSRETURN_UNDEF;
+  }
+
+  procmsg_msginfo_update_tags(msginfo, TRUE, tag_id);
+
+  XSRETURN_YES;
+}
+
+/* ClawsMail::C::unset_tag(char*) */
+static XS(XS_ClawsMail_unset_tag)
+{
+  gchar *tag_str;
+  gint tag_id;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::unset_tag");
+    XSRETURN_UNDEF;
+  }
+
+  tag_str = SvPV_nolen(ST(0));
+  tag_id = tags_get_id_for_str(tag_str);
+  if(tag_id == -1) {
+    g_warning("Perl Plugin: unset_tag requested setting of a non-existing tag");
+    XSRETURN_UNDEF;
+  }
+
+  procmsg_msginfo_update_tags(msginfo, FALSE, tag_id);
+
+  XSRETURN_YES;
+}
+
+/* ClawsMail::C::clear_tags() */
+static XS(XS_ClawsMail_clear_tags)
+{
+  dXSARGS;
+  if(items != 0) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::clear_tags");
+    XSRETURN_UNDEF;
+  }
+
+  procmsg_msginfo_clear_tags(msginfo);
+  XSRETURN_YES;
+}
+
+
+/* ClawsMail::C::make_sure_tag_exists(char*) */
+static XS(XS_ClawsMail_make_sure_tag_exists)
+{
+  gchar *tag_str;
+  gint tag_id;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::make_sure_tag_exists");
+    XSRETURN_UNDEF;
+  }
+
+  tag_str = SvPV_nolen(ST(0));
+
+  if(IS_NOT_RESERVED_TAG(tag_str) == FALSE) {
+    g_warning("Perl Plugin: Trying to create a tag with a reserved name: %s", tag_str);
+    XSRETURN_UNDEF;
+  }
+
+  tags_add_tag(tag_str);
+
+  XSRETURN_YES;
+}
+
+
+
+/* ClawsMail::C::make_sure_folder_exists(char*) */
+static XS(XS_ClawsMail_make_sure_folder_exists)
+{
+  gchar *identifier;
+  FolderItem *item;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::make_sure_folder_exists");
+    XSRETURN_UNDEF;
+  }
+
+  identifier = SvPV_nolen(ST(0));
+  item = folder_get_item_from_identifier(identifier);
+  if(item)
+    XSRETURN_YES;
+  else
+    XSRETURN_NO;
+}
+
+
+/* ClawsMail::C::addr_in_addressbook(char* [, char*]) */
+static XS(XS_ClawsMail_addr_in_addressbook)
+{
+  gchar *addr;
+  gchar *bookname;
+  gboolean found;
+
+  dXSARGS;
+  if(items != 1 && items != 2) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::addr_in_addressbook");
+    XSRETURN_UNDEF;
+  }
+
+  addr = SvPV_nolen(ST(0));
+
+  if(items == 1) {
+    found = addr_in_addressbook(addr,NULL);
+  }
+  else {
+    bookname = SvPV_nolen(ST(1));
+    found = addr_in_addressbook(addr,bookname);
+  }
+
+  if(found) {
+    filter_log_write(LOG_MATCH,"addr_in_addressbook");
+    XSRETURN_YES;
+  }
+  else
+    XSRETURN_NO;
+}
+
+
+/* Filter actions */
+
+/* ClawsMail::C::set_flag(int) */
+static XS(XS_ClawsMail_set_flag)
+{
+  int flag;
+  /* flags:  1 mark
+   *         2 mark as unread
+   *         7 lock
+   */
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::set_flag");
+    XSRETURN_UNDEF;
+  }
+  flag = SvIV(ST(0));
+
+  switch(flag) {
+  case 1:
+    MSG_SET_PERM_FLAGS(msginfo->flags, MSG_MARKED);
+    procmsg_msginfo_set_flags(msginfo, MSG_MARKED,0);
+    filter_log_write(LOG_ACTION,"mark");
+    XSRETURN_YES;
+  case 2:
+    MSG_SET_PERM_FLAGS(msginfo->flags, MSG_UNREAD);
+    procmsg_msginfo_set_flags(msginfo, MSG_UNREAD,0);
+    filter_log_write(LOG_ACTION,"mark_as_unread");
+    XSRETURN_YES;
+  case 7:
+    MSG_SET_PERM_FLAGS(msginfo->flags, MSG_LOCKED);
+    procmsg_msginfo_set_flags(msginfo, MSG_LOCKED,0);
+    filter_log_write(LOG_ACTION,"lock");
+    XSRETURN_YES;
+  default:
+    g_warning("Perl Plugin: Unknown argument to ClawsMail::C::set_flag");
+    XSRETURN_UNDEF;
+  }
+}
+
+/* ClawsMail::C::unset_flag(int) */
+static XS(XS_ClawsMail_unset_flag)
+{
+  int flag;
+  /*
+   * flags:  1 unmark
+   *         2 mark as read
+   *         7 unlock
+   */
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::unset_flag");
+    XSRETURN_UNDEF;
+  }
+  flag = SvIV(ST(0));
+
+  switch(flag) {
+  case 1:
+    MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_MARKED);
+    procmsg_msginfo_unset_flags(msginfo, MSG_MARKED,0);
+    filter_log_write(LOG_ACTION,"unmark");
+    XSRETURN_YES;
+  case 2:
+    MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_UNREAD | MSG_NEW);
+    procmsg_msginfo_unset_flags(msginfo, MSG_UNREAD | MSG_NEW,0);
+    filter_log_write(LOG_ACTION,"mark_as_read");
+    XSRETURN_YES;
+  case 7:
+    MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_LOCKED);
+    procmsg_msginfo_unset_flags(msginfo, MSG_LOCKED,0);
+    filter_log_write(LOG_ACTION,"unlock");
+    XSRETURN_YES;
+  default:
+    g_warning("Perl Plugin: Unknown argument to ClawsMail::C::unset_flag");
+    XSRETURN_UNDEF;
+  }
+}
+
+/* ClawsMail::C::move(char*) */
+static XS(XS_ClawsMail_move)
+{
+  gchar *targetfolder;
+  gchar *logtext;
+  FolderItem *dest_folder;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::move");
+    XSRETURN_UNDEF;
+  }
+
+  targetfolder = SvPV_nolen(ST(0));
+  dest_folder = folder_find_item_from_identifier(targetfolder);
+
+  if (!dest_folder) {
+    g_warning("Perl Plugin: move: folder not found '%s'",
+      targetfolder ? targetfolder :"");
+    XSRETURN_UNDEF;
+  }
+  if (folder_item_move_msg(dest_folder, msginfo) == -1) {
+    g_warning("Perl Plugin: move:  could not move message");
+    XSRETURN_UNDEF;
+  }
+  stop_filtering = TRUE;
+  logtext = g_strconcat("move to ", targetfolder, NULL);
+  filter_log_write(LOG_ACTION, logtext);
+  g_free(logtext);
+  XSRETURN_YES;
+}
+
+/* ClawsMail::C::copy(char*) */
+static XS(XS_ClawsMail_copy)
+{
+  char *targetfolder;
+  gchar *logtext;
+  FolderItem *dest_folder;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::copy");
+    XSRETURN_UNDEF;
+  }
+  targetfolder = SvPV_nolen(ST(0));
+  dest_folder = folder_find_item_from_identifier(targetfolder);
+
+  if (!dest_folder) {
+    g_warning("Perl Plugin: copy: folder not found '%s'",
+      targetfolder ? targetfolder :"");
+    XSRETURN_UNDEF;
+  }
+  if (folder_item_copy_msg(dest_folder, msginfo) == -1) {
+    g_warning("Perl Plugin: copy: could not copy message");
+    XSRETURN_UNDEF;
+  }
+  logtext = g_strconcat("copy to ", targetfolder, NULL);
+  filter_log_write(LOG_ACTION, logtext);
+  g_free(logtext);
+  XSRETURN_YES;
+}
+
+/* ClawsMail::C::delete */
+static XS(XS_ClawsMail_delete)
+{
+  dXSARGS;
+  if(items != 0) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::delete");
+    XSRETURN_UNDEF;
+  }
+  folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
+  stop_filtering = TRUE;
+  filter_log_write(LOG_ACTION, "delete");
+  XSRETURN_YES;
+}
+
+/* ClawsMail::C::hide */
+static XS(XS_ClawsMail_hide)
+{
+  dXSARGS;
+  if(items != 0) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::hide");
+    XSRETURN_UNDEF;
+  }
+  msginfo->hidden = TRUE;
+  filter_log_write(LOG_ACTION, "hide");
+  XSRETURN_YES;
+}
+
+
+/* ClawsMail::C::color(int) */
+static XS(XS_ClawsMail_color)
+{
+  int color;
+  gchar *logtext;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::color");
+    XSRETURN_UNDEF;
+  }
+  color = SvIV(ST(0));
+  procmsg_msginfo_unset_flags(msginfo, MSG_CLABEL_FLAG_MASK, 0); 
+  procmsg_msginfo_set_flags(msginfo, MSG_COLORLABEL_TO_FLAGS(color), 0);
+  MSG_SET_COLORLABEL_VALUE(msginfo->flags,color);
+
+  logtext = g_strdup_printf("color: %d", color);
+  filter_log_write(LOG_ACTION, logtext);
+  g_free(logtext);
+
+  XSRETURN_YES;
+}
+
+/* ClawsMail::C::change_score(int) */
+static XS(XS_ClawsMail_change_score)
+{
+  int score;
+  gchar *logtext;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::change_score");
+    XSRETURN_UNDEF;
+  }
+  score = SvIV(ST(0));
+  msginfo->score += score;
+
+  logtext = g_strdup_printf("change score: %+d", score);
+  filter_log_write(LOG_ACTION, logtext);
+  g_free(logtext);
+
+  XSRETURN_IV(msginfo->score);
+}
+
+/* ClawsMail::C::set_score(int) */
+static XS(XS_ClawsMail_set_score)
+{
+  int score;
+  gchar *logtext;
+
+  dXSARGS;
+  if(items != 1) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::set_score");
+    XSRETURN_UNDEF;
+  }
+  score = SvIV(ST(0));
+  msginfo->score = score;
+
+  logtext = g_strdup_printf("set score: %d", score);
+  filter_log_write(LOG_ACTION, logtext);
+  g_free(logtext);
+
+  XSRETURN_IV(msginfo->score);
+}
+
+/* ClawsMail::C::forward(int,int,char*) */
+static XS(XS_ClawsMail_forward)
+{
+  int flag;
+  /* flags:  1 forward
+   *         2 forward as attachment
+   */
+  int account_id,val;
+  char *dest;
+  gchar *logtext;
+  PrefsAccount *account;
+  Compose *compose;
+
+  dXSARGS;
+  if(items != 3) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::forward");
+    XSRETURN_UNDEF;
+  }
+
+  flag = SvIV(ST(0));
+  account_id = SvIV(ST(1));
+  dest = SvPV_nolen(ST(2));
+
+  account = account_find_from_id(account_id);
+  compose = compose_forward(account, msginfo,
+          flag == 1 ? FALSE : TRUE,
+          NULL, TRUE, TRUE);
+  compose_entry_append(compose, dest,
+           compose->account->protocol == A_NNTP ?
+           COMPOSE_NEWSGROUPS : COMPOSE_TO, PREF_NONE);
+
+  val = compose_send(compose);
+
+  if(val == 0) {
+
+    logtext = g_strdup_printf("forward%s to %s",
+            flag==2 ? " as attachment" : "",
+            dest    ? dest : "<unknown destination>");
+    filter_log_write(LOG_ACTION, logtext);
+    g_free(logtext);
+
+    XSRETURN_YES;
+  }
+  else
+    XSRETURN_UNDEF;
+}
+
+/* ClawsMail::C::redirect(int,char*) */
+static XS(XS_ClawsMail_redirect)
+{
+  int account_id,val;
+  char *dest;
+  gchar *logtext;
+  PrefsAccount *account;
+  Compose *compose;
+
+  dXSARGS;
+  if(items != 2) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::redirect");
+    XSRETURN_UNDEF;
+  }
+
+  account_id = SvIV(ST(0));
+  dest = SvPV_nolen(ST(1));
+
+  account = account_find_from_id(account_id);
+  compose = compose_redirect(account, msginfo, TRUE);
+  
+  if (compose->account->protocol == A_NNTP)
+    XSRETURN_UNDEF;
+  else
+    compose_entry_append(compose, dest, COMPOSE_TO, PREF_NONE);
+
+  val = compose_send(compose);
+  
+  if(val == 0) {
+    
+    logtext = g_strdup_printf("redirect to %s",
+            dest ? dest : "<unknown destination>");
+    filter_log_write(LOG_ACTION, logtext);
+    g_free(logtext);
+
+    XSRETURN_YES;
+  }
+  else
+    XSRETURN_UNDEF;
+}
+
+
+/* Utilities */
+
+/* ClawsMail::C::move_to_trash */
+static XS(XS_ClawsMail_move_to_trash)
+{
+  FolderItem *dest_folder;
+  
+  dXSARGS;
+  if(items != 0) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::move_to_trash");
+    XSRETURN_UNDEF;
+  }
+  dest_folder = folder_get_default_trash();
+  if (!dest_folder) {
+    g_warning("Perl Plugin: move_to_trash: Trash folder not found");
+    XSRETURN_UNDEF;
+  }
+  if (folder_item_move_msg(dest_folder, msginfo) == -1) {
+    g_warning("Perl Plugin: move_to_trash: could not move message to trash");
+    XSRETURN_UNDEF;
+  }
+  stop_filtering = TRUE;
+  filter_log_write(LOG_ACTION, "move_to_trash");
+  XSRETURN_YES;
+}
+
+/* ClawsMail::C::abort */
+static XS(XS_ClawsMail_abort)
+{
+  FolderItem *inbox_folder;
+
+  dXSARGS;
+  if(items != 0) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::abort");
+    XSRETURN_UNDEF;
+  }
+  if(!manual_filtering) {
+    inbox_folder = folder_get_default_inbox();
+    if (!inbox_folder) {
+      g_warning("Perl Plugin: abort: Inbox folder not found");
+      XSRETURN_UNDEF;
+    }
+    if (folder_item_move_msg(inbox_folder, msginfo) == -1) {
+      g_warning("Perl Plugin: abort: Could not move message to default inbox");
+      XSRETURN_UNDEF;
+    }
+    filter_log_write(LOG_ACTION, "abort -- message moved to default inbox");
+  }
+  else
+    filter_log_write(LOG_ACTION, "abort");
+
+  stop_filtering = TRUE;
+  XSRETURN_YES;
+}
+
+/* ClawsMail::C::get_attribute_value(char*,char*[,char*]) */
+static XS(XS_ClawsMail_get_attribute_value)
+{
+  char *addr;
+  char *attr;
+  char *attribute_value;
+  char *bookname;
+
+  dXSARGS;
+  if(items != 2 && items != 3) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::get_attribute_value");
+    XSRETURN_UNDEF;
+  }
+  addr = SvPV_nolen(ST(0));
+  attr = SvPV_nolen(ST(1));
+
+  if(items == 2)
+    attribute_value = get_attribute_value(addr,attr,NULL);
+  else {
+    bookname = SvPV_nolen(ST(2));
+    attribute_value = get_attribute_value(addr,attr,bookname);
+  }
+
+  if(attribute_value)
+    XSRETURN_PV(attribute_value);
+  XSRETURN_PV("");
+}
+
+/* ClawsMail::C::filter_log(char*,char*) */
+static XS(XS_ClawsMail_filter_log)
+{
+  char *text;
+  char *type;
+  
+  dXSARGS;
+  if(items != 2) {
+    g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::filter_log");
+    XSRETURN_UNDEF;
+  }
+  type = SvPV_nolen(ST(0));
+  text = SvPV_nolen(ST(1));
+  if(!strcmp(type, "LOG_ACTION"))
+    filter_log_write(LOG_ACTION, text);
+  else if(!strcmp(type, "LOG_MANUAL"))
+    filter_log_write(LOG_MANUAL, text);
+  else if(!strcmp(type, "LOG_MATCH"))
+    filter_log_write(LOG_MATCH, text);
+  else {
+    g_warning("Perl Plugin: ClawsMail::C::filter_log -- wrong first argument");
+    XSRETURN_UNDEF;
+  }  
+  XSRETURN_YES;
+}
+
+/* ClawsMail::C::filter_log_verbosity(int) */
+static XS(XS_ClawsMail_filter_log_verbosity)
+{
+  int retval;
+
+  dXSARGS;
+  if(items != 1 && items != 0) {
+    g_warning("Perl Plugin: Wrong number of arguments to "
+    "ClawsMail::C::filter_log_verbosity");
+    XSRETURN_UNDEF;
+  }
+  retval = filter_log_verbosity;
+
+  if(items == 1)
+    filter_log_verbosity = SvIV(ST(0));
+
+  XSRETURN_IV(retval);
+}
+
+/* register extensions */ 
+EXTERN_C void xs_init(pTHX)
+{
+  char *file = __FILE__;
+  dXSUB_SYS;
+  newXS("DynaLoader::boot_DynaLoader",    boot_DynaLoader,               file);
+  newXS("ClawsMail::C::filter_init",  XS_ClawsMail_filter_init,  "ClawsMail::C");
+  newXS("ClawsMail::C::check_flag",   XS_ClawsMail_check_flag,   "ClawsMail::C");
+  newXS("ClawsMail::C::age_greater",  XS_ClawsMail_age_greater,  "ClawsMail::C");
+  newXS("ClawsMail::C::age_lower",    XS_ClawsMail_age_lower,    "ClawsMail::C");
+  newXS("ClawsMail::C::tagged",       XS_ClawsMail_tagged,       "ClawsMail::C");
+  newXS("ClawsMail::C::set_flag",     XS_ClawsMail_set_flag,     "ClawsMail::C");
+  newXS("ClawsMail::C::unset_flag",   XS_ClawsMail_unset_flag,   "ClawsMail::C");
+  newXS("ClawsMail::C::delete",       XS_ClawsMail_delete,       "ClawsMail::C");
+  newXS("ClawsMail::C::move",         XS_ClawsMail_move,         "ClawsMail::C");
+  newXS("ClawsMail::C::copy",         XS_ClawsMail_copy,         "ClawsMail::C");
+  newXS("ClawsMail::C::color",        XS_ClawsMail_color,        "ClawsMail::C");
+  newXS("ClawsMail::C::colorlabel",   XS_ClawsMail_colorlabel,   "ClawsMail::C");
+  newXS("ClawsMail::C::change_score", XS_ClawsMail_change_score, "ClawsMail::C");
+  newXS("ClawsMail::C::set_score",    XS_ClawsMail_set_score,    "ClawsMail::C");
+  newXS("ClawsMail::C::hide",         XS_ClawsMail_hide,         "ClawsMail::C");
+  newXS("ClawsMail::C::forward",      XS_ClawsMail_forward,      "ClawsMail::C");
+  newXS("ClawsMail::C::redirect",     XS_ClawsMail_redirect,     "ClawsMail::C");
+  newXS("ClawsMail::C::set_tag",      XS_ClawsMail_set_tag,      "ClawsMail::C");
+  newXS("ClawsMail::C::unset_tag",    XS_ClawsMail_unset_tag,    "ClawsMail::C");
+  newXS("ClawsMail::C::clear_tags",   XS_ClawsMail_clear_tags,   "ClawsMail::C");
+  newXS("ClawsMail::C::make_sure_folder_exists",
+  XS_ClawsMail_make_sure_folder_exists,"ClawsMail::C");
+  newXS("ClawsMail::C::make_sure_tag_exists", XS_ClawsMail_make_sure_tag_exists,"ClawsMail::C");
+  newXS("ClawsMail::C::get_tags", XS_ClawsMail_get_tags,"ClawsMail::C");
+  newXS("ClawsMail::C::addr_in_addressbook",
+  XS_ClawsMail_addr_in_addressbook,"ClawsMail::C");
+  newXS("ClawsMail::C::open_mail_file",
+  XS_ClawsMail_open_mail_file,"ClawsMail::C");
+  newXS("ClawsMail::C::close_mail_file",
+  XS_ClawsMail_close_mail_file,"ClawsMail::C");
+  newXS("ClawsMail::C::get_next_header",
+  XS_ClawsMail_get_next_header,"ClawsMail::C");
+  newXS("ClawsMail::C::get_next_body_line",
+  XS_ClawsMail_get_next_body_line,"ClawsMail::C");
+  newXS("ClawsMail::C::move_to_trash",XS_ClawsMail_move_to_trash,"ClawsMail::C");
+  newXS("ClawsMail::C::abort",        XS_ClawsMail_abort,        "ClawsMail::C");
+  newXS("ClawsMail::C::get_attribute_value",
+  XS_ClawsMail_get_attribute_value,"ClawsMail::C");
+  newXS("ClawsMail::C::filter_log",   XS_ClawsMail_filter_log,   "ClawsMail::C");
+  newXS("ClawsMail::C::filter_log_verbosity",
+  XS_ClawsMail_filter_log_verbosity, "ClawsMail::C");
+}
+
+/*
+ * The workhorse.
+ * Returns: 0 on success
+ *          1 error in scriptfile or invocation of external
+ *            editor              -> retry
+ *          2 error in scriptfile -> abort
+ * (Yes, I know..)
+ */
+static int perl_load_file(void)
+{
+  gchar *args[] = {"", DO_CLEAN, NULL};
+  gchar *noargs[] = { NULL };
+  gchar *perlfilter;
+  gchar **cmdline;
+  gchar buf[1024];
+  gchar *pp;
+  STRLEN n_a;
+
+  call_argv("ClawsMail::Filter::Matcher::filter_init_",
+      G_DISCARD | G_EVAL | G_NOARGS,noargs);
+  /* check $@ */
+  if(SvTRUE(ERRSV)) {
+    debug_print("%s", SvPV(ERRSV,n_a));
+    return 1; 
+  }
+  perlfilter = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PERLFILTER, NULL);
+  args[0] = perlfilter;
+  call_argv("ClawsMail::Persistent::eval_file",
+      G_DISCARD | G_EVAL, args);
+  g_free(perlfilter);
+  if(SvTRUE(ERRSV)) {
+    AlertValue val;
+    gchar *message;
+
+    if(strstr(SvPV(ERRSV,n_a),"intended"))
+      return 0;
+
+    debug_print("%s", SvPV(ERRSV,n_a));
+    message = g_strdup_printf("Error processing Perl script file: "
+            "(line numbers may not be valid)\n%s",
+            SvPV(ERRSV,n_a));
+    val = alertpanel("Perl Plugin error",message,"Retry","Abort","Edit");
+    g_free(message);
+
+    if(val == G_ALERTOTHER) {
+      /* Open PERLFILTER in an external editor */
+      perlfilter = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PERLFILTER, NULL);
+      if (prefs_common_get_ext_editor_cmd() &&
+    (pp = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
+    *(pp + 1) == 's' && !strchr(pp + 2, '%')) {
+  g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), perlfilter);
+      }
+      else {
+  if (prefs_common_get_ext_editor_cmd())
+    g_warning("Perl Plugin: External editor command-line is invalid: `%s'",
+        prefs_common_get_ext_editor_cmd());
+  g_snprintf(buf, sizeof(buf), "emacs %s", perlfilter);
+      }
+      g_free(perlfilter);
+      cmdline = strsplit_with_quote(buf, " ", 1024);
+      execute_detached(cmdline);
+      g_strfreev(cmdline);
+      return 1;
+    }
+    else if(val == G_ALERTDEFAULT)
+      return 1;
+    else
+      return 2;
+  }
+
+  return 0;
+}
+
+
+/* let there be magic */
+static int perl_init(void)
+{
+  int exitstatus;
+  char *initialize[] = { "", "-w", "-e", "1;"};
+  /* The `persistent' module is taken from the Perl documentation
+     and has only slightly been modified. */
+  const char perl_persistent[] = {
+"package ClawsMail::Persistent;\n"
+"\n"
+"use strict;\n"
+"our %Cache;\n"
+"use Symbol qw(delete_package);\n"
+"\n"
+"sub valid_package_name {\n"
+"    my($string) = @_;\n"
+"    $string =~ s/([^A-Za-z0-9\\/])/sprintf(\"_%2x\",unpack(\"C\",$1))/eg;\n"
+"    # second pass only for words starting with a digit\n"
+"    $string =~ s|/(\\d)|sprintf(\"/_%2x\",unpack(\"C\",$1))|eg;\n"
+"    \n"
+"    # Dress it up as a real package name\n"
+"    $string =~ s|/|::|g;\n"
+"    return \"ClawsMail\" . $string;\n"
+"}\n"
+"\n"
+"sub eval_file {\n"
+"    my($file, $delete) = @_;\n"
+"    my $package = valid_package_name($file);\n"
+"    my $mtime = -M $file;\n"
+"    if(!(defined $Cache{$package}{mtime} &&\n"
+"   $Cache{$package}{mtime} <= $mtime)) {\n"
+"      delete_package($package) if defined $Cache{$package}{mtime};\n"
+"  local *FH;\n"
+"  open FH, $file or die \"Failed to open '$file': $!\";\n"
+"  local($/) = undef;\n"
+"  my $sub = <FH>;\n"
+"  close FH;\n"
+"  #wrap the code into a subroutine inside our unique package\n"
+"  my $eval = qq{package $package;\n"
+"          use ClawsMail::Filter::Matcher;\n"
+"          use ClawsMail::Filter::Action;\n"
+"          use ClawsMail::Utils;\n"
+"          sub handler { $sub; }};\n"
+"  {\n"
+"      # hide our variables within this block\n"
+"      my($file,$mtime,$package,$sub);\n"
+"      eval $eval;\n"
+"  }\n"
+"  die $@ if $@;\n"
+"  #cache it unless we're cleaning out each time\n"
+"  $Cache{$package}{mtime} = $mtime unless $delete;\n"
+"    }\n"
+"    eval {$package->handler;};\n"
+"    die $@ if $@;\n"
+"    delete_package($package) if $delete;\n"
+"}\n"
+  };
+  const char perl_filter_matcher[] = {
+"BEGIN {$INC{'ClawsMail/Filter/Matcher.pm'} = 1;}\n"
+"package ClawsMail::Filter::Matcher;\n"
+"use locale;\n"
+"use base qw(Exporter);\n"
+"use strict;\n"
+"our @EXPORT =   (qw(header body filepath manual),\n"
+"     qw(filter_log_verbosity filter_log),\n"
+"     qw(all marked unread deleted new replied),\n"
+"     qw(forwarded locked colorlabel match matchcase),\n"
+"     qw(regexp regexpcase test),\n"
+"     qw(to cc subject from to_or_cc newsgroups inreplyto),\n"
+"     qw(references body_part headers_part message),\n"
+"     qw(size_greater size_smaller size_equal),\n"
+"     qw(score_greater score_lower score_equal),\n"
+"     qw(age_greater age_lower partial tagged $permanent));\n"
+"# Global Variables\n"
+"our(%header,$body,%msginfo,$mail_done,$manual);\n"
+"our %colors = ('none'     =>  0,'orange'   =>  1,'red'  =>  2,\n"
+"            'pink'     =>  3,'sky blue' =>  4,'blue' =>  5,\n"
+"             'green'    =>  6,'brown'    =>  7);\n"
+"# For convenience\n"
+"sub lc2_ {\n"
+"    my $arg = shift;\n"
+"    if(defined $arg) {\n"
+"        return lc $arg;\n"
+"    }\n"
+"    else {\n"
+"        return \"\";\n"
+"    }\n"
+"}\n"
+"sub to           { return \"to\";            }\n"
+"sub cc           { return \"cc\";            }\n"
+"sub from         { return \"from\";          }\n"
+"sub subject      { return \"subject\";       }\n"
+"sub to_or_cc     { return \"to_or_cc\";      }\n"
+"sub newsgroups   { return \"newsgroups\";    }\n"
+"sub inreplyto    { return \"in-reply-to\";   }\n"
+"sub references   { return \"references\";    }\n"
+"sub body_part    { return \"body_part\";     }\n"
+"sub headers_part { return \"headers_part\";  }\n"
+"sub message      { return \"message\";       }\n"
+"# access the mail directly\n"
+"sub header {\n"
+"    my $key = shift;\n"
+"    if(not defined $key) {\n"
+"  init_();\n"
+"  return keys %header;\n"
+"    }\n"
+"    $key = lc2_ $key; $key =~ s/:$//;\n"
+"    init_() unless exists $header{$key};\n"
+"    if(exists $header{$key}) {\n"
+"  wantarray ? return @{$header{$key}} : return $header{$key}->[-1];\n"
+"    }\n"
+"    return undef;\n"
+"}\n"
+"sub body {init_();return $body;}\n"
+"sub filepath {return $msginfo{\"filepath\"};}\n"
+"sub manual {\n"
+"    ClawsMail::C::filter_log(\"LOG_MATCH\",\"manual\") if $manual;\n"
+"    return $manual;\n"
+"}\n"
+"sub filter_log {\n"
+"    my $arg1 = shift;\n"
+"    my $arg2 = shift;\n"
+"    return ClawsMail::C::filter_log($arg1,$arg2)\n"
+"  if defined($arg2);\n"
+"    return ClawsMail::C::filter_log(\"LOG_MANUAL\",$arg1);\n"
+"}\n"
+"sub filter_log_verbosity {\n"
+"    $_ = shift;\n"
+"    return ClawsMail::C::filter_log_verbosity($_)\n"
+"  if defined($_);\n"
+"    return ClawsMail::C::filter_log_verbosity();\n"
+"}\n"
+"# Public Matcher Tests\n"
+"sub all { ClawsMail::C::filter_log(\"LOG_MATCH\",\"all\");return 1; }\n"
+"sub marked        { return ClawsMail::C::check_flag(1);}\n"
+"sub unread        { return ClawsMail::C::check_flag(2);}\n"
+"sub deleted       { return ClawsMail::C::check_flag(3);}\n"
+"sub new           { return ClawsMail::C::check_flag(4);}\n"
+"sub replied       { return ClawsMail::C::check_flag(5);}\n"
+"sub forwarded     { return ClawsMail::C::check_flag(6);}\n"
+"sub locked        { return ClawsMail::C::check_flag(7);}\n"
+"sub ignore_thread { return ClawsMail::C::check_flag(8);}\n"
+"sub age_greater  {return ClawsMail::C::age_greater(@_);}\n"
+"sub age_lower    {return ClawsMail::C::age_lower(@_);  }\n"
+"sub tagged       {return ClawsMail::C::tagged(@_);  }\n"
+"sub score_equal {\n"
+"    my $my_score = shift;\n"
+"    return 0 unless (defined($msginfo{\"score\"}) and defined($my_score));\n"
+"    if($my_score == $msginfo{\"score\"}) {\n"
+"  ClawsMail::C::filter_log(\"LOG_MATCH\",\"score_equal\");\n"
+"  return 1;\n"
+"    }else{return 0;}\n"
+"}\n"
+"sub score_greater {\n"
+"    my $my_score = shift;\n"
+"    return 0 unless (defined($msginfo{\"score\"}) and defined($my_score));\n"
+"    if($msginfo{\"score\"} > $my_score) {\n"
+"  ClawsMail::C::filter_log(\"LOG_MATCH\",\"score_greater\");\n"
+"  return 1;\n"
+"    }else{return 0;}\n"
+"}\n"
+"sub score_lower {\n"
+"    my $my_score = shift;\n"
+"    return 0 unless (defined($msginfo{\"score\"}) and defined($my_score));\n"
+"    if($msginfo{\"score\"} < $my_score) {\n"
+"  ClawsMail::C::filter_log(\"LOG_MATCH\",\"score_lower\");\n"
+"  return 1;\n"
+"    }else{return 0;}\n"
+"}\n"
+"sub colorlabel {\n"
+"    my $color = shift;\n"
+"    $color = lc2_ $color;\n"
+"    $color = $colors{$color} if exists $colors{$color};\n"
+"    $color = 0 if $color =~ m/\\D/;\n"
+"    return ClawsMail::C::colorlabel($color);\n"
+"}\n"
+"sub size_greater {\n"
+"    my $my_size = shift;\n"
+"    return 0 unless (defined($msginfo{\"size\"}) and defined($my_size));\n"
+"    if($msginfo{\"size\"} > $my_size) {\n"
+"  ClawsMail::C::filter_log(\"LOG_MATCH\",\"size_greater\");\n"
+"  return 1;\n"
+"    }else{return 0;}\n"
+"}\n"
+"sub size_smaller {\n"
+"    my $my_size = shift;\n"
+"    return 0 unless (defined($msginfo{\"size\"}) and defined($my_size));\n"
+"    if($msginfo{\"size\"} < $my_size) {\n"
+"  ClawsMail::C::filter_log(\"LOG_MATCH\",\"size_smaller\");\n"
+"  return 1;\n"
+"    }else{return 0;}\n"
+"}\n"
+"sub size_equal {\n"
+"    my $my_size = shift;\n"
+"    return 0 unless (defined($msginfo{\"size\"}) and defined($my_size));\n"
+"    if($msginfo{\"size\"} == $my_size) {\n"
+"  ClawsMail::C::filter_log(\"LOG_MATCH\",\"size_equal\");\n"
+"  return 1;\n"
+"    }else{return 0;}\n"
+"}\n"
+"sub partial {\n"
+"    return 0 unless defined($msginfo{\"total_size\"})\n"
+"  and defined($msginfo{\"size\"});\n"
+"    if($msginfo{\"total_size\"} != 0\n"
+"       && $msginfo{\"size\"} != $msginfo{\"total_size\"}) {\n"
+"  ClawsMail::C::filter_log(\"LOG_MATCH\",\"partial\");\n"
+"  return 1;\n"
+"    }else{return 0;}\n"
+"}\n"
+"sub test {\n"
+"   $_ = shift; my $command = \"\"; my $hl=\"\"; my $re=\"\"; my $retval;\n"
+"   my $cmdline = $_;\n"
+"   s/\\\"/\"/g; #fool stupid emacs perl mode\";\n"
+"   s/([^%]*)//; $command .= $1;\n"
+"   while($_) {\n"
+"       if   (/^%%/){s/^%%([^%]*)//;$command .= \"\\\\%\".$1; next;}\n"
+"       elsif(/^%s/){s/^%s([^%]*)//;$hl=header(\"subject\");$re=$1;}\n"
+"       elsif(/^%f/){s/^%f([^%]*)//;$hl=header(\"from\");$re=$1;}\n"
+"       elsif(/^%t/){s/^%t([^%]*)//;$hl=header(\"to\");$re=$1;}\n"
+"       elsif(/^%c/){s/^%c([^%]*)//;$hl=header(\"cc\");$re=$1;}\n"
+"       elsif(/^%d/){s/^%d([^%]*)//;$hl=header(\"date\");$re=$1;}\n"
+"       elsif(/^%i/){s/^%i([^%]*)//;$hl=header(\"message-id\");$re=$1;}\n"
+"       elsif(/^%n/){s/^%n([^%]*)//;$hl=header(\"newsgroups\");$re=$1;}\n"
+"       elsif(/^%r/){s/^%r([^%]*)//;$hl=header(\"references\");$re=$1;}\n"
+"       elsif(/^%F/){s/^%F([^%]*)//;$hl=filepath();$re=$1;}\n"
+"       else        {s/^(%[^%]*)//; $command .= $1;}\n"
+"       $command .= \"\\Q$hl\\E\" if defined $hl;$hl=\"\";\n"
+"       $command .= $re;$re=\"\";\n"
+"   }\n"
+"   $retval = !(system($command)>>8);\n"
+"   ClawsMail::C::filter_log(\"LOG_MATCH\",\"test: $cmdline\")\n"
+"       if $retval;\n"
+"   return $retval;\n"
+"}\n"
+"sub matchcase {\n"
+"    my $retval;\n"
+"    $retval = match_(@_,\"i\");\n"
+"    ClawsMail::C::filter_log(\"LOG_MATCH\",\"matchcase: $_[0], $_[1]\")\n"
+"  if $retval;\n"
+"    return $retval;\n"
+"}\n"
+"sub match {\n"
+"    my $retval;\n"
+"    $retval = match_(@_);\n"
+"    ClawsMail::C::filter_log(\"LOG_MATCH\",\"match: $_[0], $_[1]\")\n"
+"  if $retval;\n"
+"    return $retval;\n"
+"}\n"
+"sub regexpcase {\n"
+"    my $retval;\n"
+"    $retval = match_(@_,\"ri\");\n"
+"    ClawsMail::C::filter_log(\"LOG_MATCH\",\"regexpcase: $_[0], $_[1]\")\n"
+"  if $retval;\n"
+"    return $retval;\n"
+"}\n"
+"sub regexp {\n"
+"    my $retval;\n"
+"    $retval = match_(@_,\"r\");\n"
+"    ClawsMail::C::filter_log(\"LOG_MATCH\",\"regexp: $_[0], $_[1]\")\n"
+"  if $retval;\n"
+"    return $retval;\n"
+"}\n"
+"# Internals\n"
+"sub add_header_entries_ {\n"
+"    my($key,@values) = @_; $key = lc2_ $key; $key =~ s/:$//;\n"
+"    $header{$key} = [] unless exists $header{$key};\n"
+"    push @{$header{$key}},@values;\n"
+"}\n"
+"# read whole mail\n"
+"sub init_ {\n"
+"    return 0 if $mail_done;\n"
+"    ClawsMail::C::open_mail_file();\n"
+"    read_headers_();\n"
+"    read_body_();\n"
+"    ClawsMail::C::close_mail_file();\n"
+"    $mail_done = 1;\n"
+"}\n"
+"sub filter_init_ {\n"
+"    %header = (); %msginfo = (); undef $body; $mail_done = 0;\n"
+"    $manual                        = ClawsMail::C::filter_init(100);\n"
+"    $msginfo{\"size\"}               = ClawsMail::C::filter_init( 1) ;\n"
+"    add_header_entries_(\"date\",      ClawsMail::C::filter_init( 2));\n"
+"    add_header_entries_(\"from\",      ClawsMail::C::filter_init( 3));\n"
+"    add_header_entries_(\"to\",        ClawsMail::C::filter_init( 4));\n"
+"    add_header_entries_(\"cc\",        ClawsMail::C::filter_init( 5));\n"
+"    add_header_entries_(\"newsgroups\",ClawsMail::C::filter_init( 6));\n"
+"    add_header_entries_(\"subject\",   ClawsMail::C::filter_init( 7));\n"
+"    add_header_entries_(\"msgid\",     ClawsMail::C::filter_init( 8));\n"
+"    add_header_entries_(\"inreplyto\", ClawsMail::C::filter_init( 9));\n"
+"    add_header_entries_(\"xref\",      ClawsMail::C::filter_init(10));\n"
+"    add_header_entries_(\"xface\",     ClawsMail::C::filter_init(11));\n"
+"    add_header_entries_(\"dispositionnotificationto\",\n"
+"                   ClawsMail::C::filter_init(12));\n"
+"    add_header_entries_(\"returnreceiptto\",\n"
+"                   ClawsMail::C::filter_init(13));\n"
+"    add_header_entries_(\"references\",ClawsMail::C::filter_init(14));\n"
+"    $msginfo{\"score\"}              = ClawsMail::C::filter_init(15);\n"
+"    $msginfo{\"plaintext_file\"}     = ClawsMail::C::filter_init(17);\n"
+"    $msginfo{\"hidden\"}             = ClawsMail::C::filter_init(19);\n"
+"    $msginfo{\"filepath\"}           = ClawsMail::C::filter_init(20);\n"
+"    $msginfo{\"partial_recv\"}       = ClawsMail::C::filter_init(21);\n"
+"    $msginfo{\"total_size\"}         = ClawsMail::C::filter_init(22);\n"
+"    $msginfo{\"account_server\"}     = ClawsMail::C::filter_init(23);\n"
+"    $msginfo{\"account_login\"}      = ClawsMail::C::filter_init(24);\n"
+"    $msginfo{\"planned_download\"}   = ClawsMail::C::filter_init(25);\n"
+"} \n"
+"sub read_headers_ {\n"
+"    my($key,$value);\n"
+"    %header = ();\n"
+"    while(($key,$value) = ClawsMail::C::get_next_header()) {\n"
+"  next unless $key =~ /:$/;\n"
+"  add_header_entries_($key,$value);\n"
+"    }\n"
+"}\n"
+"sub read_body_ {\n"
+"    my $line;\n"
+"    while(defined($line = ClawsMail::C::get_next_body_line())) {\n"
+"  $body .= $line;\n"
+"    }    \n"
+"}\n"
+"sub match_ {\n"
+"  my ($where,$what,$modi) = @_; $modi ||= \"\";\n"
+"  my $nocase=\"\"; $nocase = \"1\" if (index($modi,\"i\") != -1);\n"
+"  my $regexp=\"\"; $regexp = \"1\" if (index($modi,\"r\") != -1);\n"
+"  if($where eq \"to_or_cc\") {\n"
+"    if(not $regexp) { \n"
+"      return ((index(header(\"to\"),$what) != -1) or\n"
+"        (index(header(\"cc\"),$what) != -1)) unless $nocase;\n"
+"      return ((index(lc2_(header(\"to\")),lc2_($what)) != -1) or\n"
+"        (index(lc2_(header(\"cc\")),lc2_($what)) != -1))\n"
+"    } else {\n"
+"      return ((header(\"to\") =~ m/$what/) or\n"
+"        (header(\"cc\") =~ m/$what/)) unless $nocase;\n"
+"      return ((header(\"to\") =~ m/$what/i) or\n"
+"        (header(\"cc\") =~ m/$what/i));\n"
+"    }\n"
+"  } elsif($where eq \"body_part\") {\n"
+"    my $mybody = body(); $mybody =~ s/\\s+/ /g;\n"
+"    if(not $regexp) {\n"
+"      return (index($mybody,$what) != -1) unless $nocase;\n"
+"      return (index(lc2_($mybody),lc2_($what)) != -1);\n"
+"    } else {\n"
+"      return ($body =~ m/$what/) unless $nocase;\n"
+"      return ($body =~ m/$what/i);\n"
+"    }\n"
+"  } elsif($where eq \"headers_part\") {\n"
+"    my $myheader = header_as_string_();\n"
+"    if(not $regexp) {\n"
+"      $myheader =~ s/\\s+/ /g;\n"
+"      return (index($myheader,$what) != -1) unless $nocase;\n"
+"      return (index(lc2_($myheader),lc2_($what)) != -1);\n"
+"    } else {\n"
+"      return ($myheader =~ m/$what/) unless $nocase;\n"
+"      return ($myheader =~ m/$what/i);\n"
+"    }\n"
+"  } elsif($where eq \"message\") {\n"
+"    my $message = header_as_string_();\n"
+"    $message .= \"\\n\".body();\n"
+"    if(not $regexp) {\n"
+"      $message =~ s/\\s+/ /g;\n"
+"      return (index($message,$what) != -1) unless $nocase;\n"
+"      return (index(lc2_($message),lc2_($what)) != -1);\n"
+"    } else {\n"
+"      return ($message =~ m/$what/) unless $nocase;\n"
+"      return ($message =~ m/$what/i);\n"
+"    }\n"
+"  } elsif($where eq \"tag\") {\n"
+"    my $found = 0;\n"
+"    sub ClawsMail::Utils::get_tags;"
+"    foreach my $tag (ClawsMail::Utils::get_tags) {\n"
+"      if(not $regexp) {\n"
+"        if($nocase) {\n"
+"          $found = (index(lc2_($tag),lc2_($what)) != -1);\n"
+"        } else {\n"
+"          $found = (index($tag,$what) != -1);\n"
+"        }\n"
+"      } else {\n"
+"        if ($nocase) {\n"
+"          $found = ($tag =~ m/$what/i);\n"
+"        } else {\n"
+"          $found = ($tag =~ m/$what/);\n"
+"        }\n"
+"      }\n"
+"      last if $found;\n"
+"    }\n"
+"    return $found;"
+"  } else {\n"
+"    $where = lc2_ $where;\n"
+"    my $myheader = header(lc2_ $where); $myheader ||= \"\";\n"
+"    return 0 unless $myheader;\n"
+"    if(not $regexp) {    \n"
+"      return (index(header($where),$what) != -1) unless $nocase;\n"
+"      return (index(lc2_(header($where)),lc2_($what)) != -1);\n"
+"    } else {\n"
+"      return (header($where) =~ m/$what/) unless $nocase;\n"
+"      return (header($where) =~ m/$what/i);\n"
+"    } \n"
+"  }\n"
+"}\n"
+"sub header_as_string_ {\n"
+"    my $headerstring=\"\";\n"
+"    my @headerkeys = header(); my(@fields,$field);\n"
+"    foreach $field (@headerkeys) {\n"
+"  @fields = header($field);\n"
+"  foreach (@fields) {\n"
+"      $headerstring .= $field.\": \".$_.\"\\n\";\n"
+"  }\n"
+"    }\n"
+"    return $headerstring;\n"
+"}\n"
+"our $permanent = \"\";\n"
+"1;\n"
+  };
+  const char perl_filter_action[] = {
+"BEGIN {$INC{'ClawsMail/Filter/Action.pm'} = 1;}\n"
+"package ClawsMail::Filter::Action;\n"
+"use base qw(Exporter);\n"
+"our @EXPORT = (qw(mark unmark dele mark_as_unread mark_as_read),\n"
+"         qw(lock unlock move copy color execute),\n"
+"         qw(hide set_score change_score stop exit),\n"
+"         qw(forward forward_as_attachment redirect),\n"
+"        qw(set_tag unset_tag clear_tags),\n"
+"         );\n"
+"our %colors = ('none'     =>  0,'orange' =>  1,\n"
+"             'red'      =>  2,'pink'   =>  3,\n"
+"             'sky blue' =>  4,'blue'   =>  5,\n"
+"             'green'    =>  6,'brown'  =>  7);\n"
+"sub mark           { ClawsMail::C::set_flag  (1);}\n"
+"sub unmark         { ClawsMail::C::unset_flag(1);}\n"
+"sub mark_as_unread { ClawsMail::C::set_flag  (2);}\n"
+"sub mark_as_read   { ClawsMail::C::unset_flag(2);}\n"
+"sub lock           { ClawsMail::C::set_flag  (7);}\n"
+"sub unlock         { ClawsMail::C::unset_flag(7);}\n"
+"sub copy           { ClawsMail::C::copy     (@_);}\n"
+"sub forward        { ClawsMail::C::forward(1,@_);}\n"
+"sub forward_as_attachment {ClawsMail::C::forward(2,@_);}\n"
+"sub redirect       { ClawsMail::C::redirect(@_); }\n"
+"sub hide           { ClawsMail::C::hide();       }\n"
+"sub exit {\n"
+"    ClawsMail::C::filter_log(\"LOG_ACTION\",\"exit\");\n"
+"    stop(1);\n"
+"}\n"
+"sub stop {\n"
+"    my $nolog = shift;\n"
+"    ClawsMail::C::filter_log(\"LOG_ACTION\", \"stop\")\n"
+"  unless defined($nolog);\n"
+"    die 'intended';\n"
+"}\n"
+"sub set_score {\n"
+"    $ClawsMail::Filter::Matcher::msginfo{\"score\"} =\n"
+"  ClawsMail::C::set_score(@_);\n"
+"}\n"
+"sub change_score {\n"
+"    $ClawsMail::Filter::Matcher::msginfo{\"score\"} =\n"
+"  ClawsMail::C::change_score(@_);\n"
+"}\n"
+"sub execute {\n"
+"    my $flv; my $cmd = shift; return 0 unless defined($cmd);\n"
+"    $flv = ClawsMail::C::filter_log_verbosity(0);\n"
+"    ClawsMail::Filter::Matcher::test($cmd);\n"
+"    ClawsMail::C::filter_log_verbosity($flv);\n"
+"    ClawsMail::C::filter_log(\"LOG_ACTION\", \"execute: $cmd\");\n"
+"    1;\n"
+"}\n"
+"sub move { ClawsMail::C::move(@_); stop(1);}\n"
+"sub dele { ClawsMail::C::delete(); stop(1);}\n"
+"sub color {\n"
+"    ($color) = @_;$color = lc2_ $color;\n"
+"    $color = $colors{$color} if exists $colors{$color};\n"
+"    $color = 0 if $color =~ m/\\D/;\n"
+"    ClawsMail::C::color($color);\n"
+"}\n"
+"sub set_tag { ClawsMail::C::set_tag(@_);}\n"
+"sub unset_tag { ClawsMail::C::unset_tag(@_);}\n"
+"sub clear_tags { ClawsMail::C::clear_tags(@_);}\n"
+"1;\n"
+  };
+  const char perl_utils[] = {
+"BEGIN {$INC{'ClawsMail/Utils.pm'} = 1;}\n"
+"package ClawsMail::Utils;\n"
+"use base qw(Exporter);\n"
+"our @EXPORT = (\n"
+"             qw(SA_is_spam extract_addresses move_to_trash abort),\n"
+"             qw(addr_in_addressbook from_in_addressbook),\n"
+"             qw(get_attribute_value make_sure_folder_exists),\n"
+"            qw(make_sure_tag_exists get_tags),\n"
+"             );\n"
+"# Spam\n"
+"sub SA_is_spam {\n"
+"    my $retval;\n"
+"    $retval = not ClawsMail::Filter::Matcher::test('spamc -c < %F > /dev/null');\n"
+"    ClawsMail::C::filter_log(\"LOG_MATCH\",\"SA_is_spam\") if $retval;\n"
+"    return $retval;\n"
+"}\n"
+"# simple extract email addresses from a header field\n"
+"sub extract_addresses {\n"
+"  my $hf = shift; return undef unless defined($hf);\n"
+"  my @addr = ();\n"
+"  while($hf =~ m/[-.+\\w]+\\@[-.+\\w]+/) {\n"
+"    $hf =~ s/^.*?([-.+\\w]+\\@[-.+\\w]+)//;\n"
+"    push @addr,$1;\n"
+"  }\n"
+"  push @addr,\"\" unless @addr;\n"
+"  return @addr;\n"
+"}\n"
+"# move to trash\n"
+"sub move_to_trash {\n"
+"    ClawsMail::C::move_to_trash();\n"
+"    ClawsMail::Filter::Action::stop(1);\n"
+"}\n"
+"# make sure a folder with a given id exists\n"
+"sub make_sure_folder_exists {\n"
+"    ClawsMail::C::make_sure_folder_exists(@_);\n"
+"}\n"
+"sub make_sure_tag_exists {\n"
+"    ClawsMail::C::make_sure_tag_exists(@_);\n"
+"}\n"
+"sub get_tags {\n"
+"    ClawsMail::C::get_tags(@_);\n"
+"}\n"
+"# abort: stop() and do not continue with built-in filtering\n"
+"sub abort {\n"
+"    ClawsMail::C::abort();\n"
+"    ClawsMail::Filter::Action::stop(1);\n"
+"}\n"
+"# addressbook query\n"
+"sub addr_in_addressbook {\n"
+"    return ClawsMail::C::addr_in_addressbook(@_) if @_;\n"
+"    return 0;\n"
+"}\n"
+"sub from_in_addressbook {\n"
+"    my ($from) = extract_addresses(ClawsMail::Filter::Matcher::header(\"from\"));\n"
+"    return 0 unless $from;\n"
+"    return addr_in_addressbook($from,@_);\n"
+"}\n"
+"sub get_attribute_value {\n"
+"    my $email = shift; my $key = shift;\n"
+"    return \"\" unless ($email and $key);\n"
+"    return ClawsMail::C::get_attribute_value($email,$key,@_);\n"
+"}\n"
+"1;\n"
+  };
+
+  if((my_perl = perl_alloc()) == NULL) {
+    g_warning("Perl Plugin: Not enough memory to allocate Perl interpreter");
+    return -1;
+  }
+  PL_perl_destruct_level = 1;
+  perl_construct(my_perl);
+
+  exitstatus = perl_parse(my_perl, xs_init, 4, initialize, NULL);
+  PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
+  eval_pv(perl_filter_matcher,TRUE);
+  eval_pv(perl_filter_action,TRUE);
+  eval_pv(perl_persistent,TRUE);
+  eval_pv(perl_utils,TRUE);
+  return exitstatus;
+}
+
+static gboolean my_filtering_hook(gpointer source, gpointer data)
+{
+  int retry;
+
+  g_return_val_if_fail(source != NULL, FALSE);
+
+  mail_filtering_data = (MailFilteringData *) source;
+  msginfo = mail_filtering_data->msginfo;
+  if (!msginfo)
+    return FALSE;
+  stop_filtering = FALSE;
+  wrote_filter_log_head = FALSE;
+  filter_log_verbosity = config.filter_log_verbosity;
+  if(GPOINTER_TO_UINT(data) == AUTO_FILTER)
+    manual_filtering = FALSE;
+  else if(GPOINTER_TO_UINT(data) == MANU_FILTER)
+    manual_filtering = TRUE;
+  else
+    debug_print("Invalid user data ignored.\n");
+
+  if(!manual_filtering)
+    statusbar_print_all("Perl Plugin: filtering message...");
+
+  /* Process Skript File */
+  retry = perl_load_file();
+  while(retry == 1) {
+    debug_print("Error processing Perl script file. Retrying..\n");
+    retry = perl_load_file();
+  }
+  if(retry == 2) {
+    debug_print("Error processing Perl script file. Aborting..\n");
+    stop_filtering = FALSE;
+  }
+  return stop_filtering;
+}
+
+static void perl_plugin_save_config(void)
+{
+  PrefFile *pfile;
+  gchar *rcpath;
+
+  debug_print("Saving Perl Plugin Configuration\n");
+
+  rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+  pfile = prefs_write_open(rcpath);
+  g_free(rcpath);
+  if (!pfile || (prefs_set_block_label(pfile, "PerlPlugin") < 0))
+    return;
+  
+  if (prefs_write_param(param, pfile->fp) < 0) {
+    g_warning("Perl Plugin: Failed to write Perl Plugin configuration to file");
+    prefs_file_close_revert(pfile);
+    return;
+  }
+        if (fprintf(pfile->fp, "\n") < 0) {
+    FILE_OP_ERROR(rcpath, "fprintf");
+    prefs_file_close_revert(pfile);
+  } else
+          prefs_file_close(pfile);
+}
+
+gint plugin_init(gchar **error)
+{
+  int argc;
+  char **argv;
+  char **env;
+  int status = 0;
+  FILE *fp;
+  gchar *perlfilter;
+  gchar *rcpath;
+
+  /* version check */
+  if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,4,6),
+        VERSION_NUMERIC, "Perl", error))
+    return -1;
+
+  /* register hook for automatic and manual filtering */
+  filtering_hook_id = hooks_register_hook(MAIL_FILTERING_HOOKLIST,
+            my_filtering_hook,
+            GUINT_TO_POINTER(AUTO_FILTER));
+  if(filtering_hook_id == (guint) -1) {
+    *error = g_strdup("Failed to register mail filtering hook");
+    return -1;
+  }
+  manual_filtering_hook_id = hooks_register_hook(MAIL_MANUAL_FILTERING_HOOKLIST,
+             my_filtering_hook,
+             GUINT_TO_POINTER(MANU_FILTER));
+  if(manual_filtering_hook_id == (guint) -1) {
+    hooks_unregister_hook(MAIL_FILTERING_HOOKLIST, filtering_hook_id);
+    *error = g_strdup("Failed to register manual mail filtering hook");
+    return -1;
+  }
+
+  rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+  prefs_read_config(param, "PerlPlugin", rcpath, NULL);
+  g_free(rcpath);
+
+  /* make sure we have at least an empty scriptfile */
+  perlfilter = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PERLFILTER, NULL);
+  if((fp = fopen(perlfilter, "a")) == NULL) {
+    *error = g_strdup("Failed to create blank scriptfile");
+    g_free(perlfilter);
+    hooks_unregister_hook(MAIL_FILTERING_HOOKLIST,
+        filtering_hook_id);
+    hooks_unregister_hook(MAIL_MANUAL_FILTERING_HOOKLIST,
+        manual_filtering_hook_id);
+    return -1;
+  }
+  /* chmod for security */
+  if (change_file_mode_rw(fp, perlfilter) < 0) {
+    FILE_OP_ERROR(perlfilter, "chmod");
+    g_warning("Perl Plugin: Can't change file mode");
+  }
+  fclose(fp);
+  g_free(perlfilter);
+
+  argc = 1;
+  argv = g_new0(char*, 1);
+  argv[0] = NULL;
+  env = g_new0(char*, 1);
+  env[0] = NULL;
+
+
+  /* Initialize Perl Interpreter */
+  PERL_SYS_INIT3(&argc, &argv, &env);
+  g_free(argv);
+  g_free(env);
+  if(my_perl == NULL)
+    status = perl_init();
+  if(status) {
+    *error = g_strdup("Failed to load Perl Interpreter\n");
+    hooks_unregister_hook(MAIL_FILTERING_HOOKLIST,
+        filtering_hook_id);
+    hooks_unregister_hook(MAIL_MANUAL_FILTERING_HOOKLIST,
+        manual_filtering_hook_id);
+    return -1;
+  }
+
+  perl_gtk_init();
+  debug_print("Perl Plugin loaded\n");
+  return 0;
+}
+
+gboolean plugin_done(void)
+{
+  hooks_unregister_hook(MAIL_FILTERING_HOOKLIST,
+      filtering_hook_id);
+  hooks_unregister_hook(MAIL_MANUAL_FILTERING_HOOKLIST,
+      manual_filtering_hook_id);
+  
+  free_all_lists();
+
+  if(my_perl != NULL) {
+    PL_perl_destruct_level = 1;
+    perl_destruct(my_perl);
+    perl_free(my_perl);
+  }
+  PERL_SYS_TERM();
+
+  perl_plugin_save_config();
+
+  perl_gtk_done();
+  debug_print("Perl Plugin unloaded\n");
+  return TRUE;
+}
+
+const gchar *plugin_name(void)
+{
+  return "Perl";
+}
+
+const gchar *plugin_desc(void)
+{
+  return "This plugin provides a Perl scripting "
+    "interface for mail filters.\nFeedback "
+    "to <berndth@gmx.de> is welcome.";
+}
+
+const gchar *plugin_type(void)
+{
+  return "GTK2";
+}
+
+const gchar *plugin_licence(void)
+{
+  return "GPL3+";
+}
+
+const gchar *plugin_version(void)
+{
+  return VERSION;
+}
+
+struct PluginFeature *plugin_provides(void)
+{
+  static struct PluginFeature features[] =
+    { {PLUGIN_FILTERING, N_("Perl integration")},
+      {PLUGIN_NOTHING, NULL}};
+  return features;
+}
diff --git a/src/plugins/perl/perl_plugin.h b/src/plugins/perl/perl_plugin.h
new file mode 100644 (file)
index 0000000..f2d6a9f
--- /dev/null
@@ -0,0 +1,50 @@
+/* Perl plugin -- Perl Support for Claws Mail
+ *
+ * Copyright (C) 2004-2007 Holger Berndt
+ *
+ * Sylpheed and Claws-Mail are GTK+ based, lightweight, and fast e-mail clients
+ * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail Team
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SC_PERL_PLUGIN_H
+#define SC_PERL_PLUGIN_H SC_PERL_PLUGIN_H
+
+/* the name of the filtering Perl script file */
+#define PERLFILTER "perl_filter"
+
+typedef struct {
+  gchar *address;
+  gchar *bookname;
+} PerlPluginEmailEntry;
+
+typedef struct {
+  gchar *address;
+  gchar *value;
+  gchar *bookname;
+} PerlPluginAttributeEntry;
+
+typedef struct {
+  GSList *g_slist;
+  time_t mtime;
+} PerlPluginTimedSList;
+
+typedef struct {
+  gint filter_log_verbosity;
+} PerlPluginConfig;
+
+gint execute_detached(gchar**);
+
+#endif /* include guard */
diff --git a/src/plugins/perl/placeholder.txt b/src/plugins/perl/placeholder.txt
deleted file mode 100644 (file)
index 3b94f91..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Placeholder
diff --git a/src/plugins/python/Makefile.am b/src/plugins/python/Makefile.am
new file mode 100644 (file)
index 0000000..605e6b6
--- /dev/null
@@ -0,0 +1,45 @@
+plugindir = $(pkglibdir)/plugins
+
+plugin_LTLIBRARIES = python_plugin.la
+
+python_plugin_la_SOURCES = \
+       clawsmailmodule.c \
+       clawsmailmodule.h \
+       composewindowtype.c \
+       composewindowtype.h \
+       foldertype.c \
+       foldertype.h \
+       messageinfotype.c \
+       messageinfotype.h \
+       nodetype.c \
+       nodetype.h \
+       python_plugin.c \
+       python-hooks.c \
+       python-hooks.h \
+       python-shell.c \
+       python-shell.h \
+       gettext.h
+
+python_plugin_la_LDFLAGS = \
+       -avoid-version -module \
+       $(GLIB_LIBS) \
+       $(GTK_LIBS) \
+       $(PYGTK_LIBS) \
+       $(PYTHON_LIBS)
+
+INCLUDES = \
+       -I$(top_srcdir)/src \
+       -I$(top_srcdir)/src/common \
+       -I$(top_srcdir)/src/gtk
+
+AM_CPPFLAGS = \
+       -Wall \
+       $(CLAWS_MAIL_CFLAGS) \
+       $(GLIB_CFLAGS) \
+       $(GTK_CFLAGS) \
+       $(PYTHON_CFLAGS) \
+       $(PYGTK_CFLAGS) \
+       -DPYTHON_SHARED_LIB="\"$(PYTHON_SHARED_LIB)\"" \
+       -DENABLE_PYTHON \
+       -fno-strict-aliasing \
+       -DLOCALEDIR=\""$(localedir)"\"
diff --git a/src/plugins/python/clawsmailmodule.c b/src/plugins/python/clawsmailmodule.c
new file mode 100644 (file)
index 0000000..e0a3a5d
--- /dev/null
@@ -0,0 +1,764 @@
+/* Python plugin for Claws-Mail
+ * Copyright (C) 2009-2012 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "clawsmailmodule.h"
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "nodetype.h"
+#include "composewindowtype.h"
+#include "foldertype.h"
+#include "messageinfotype.h"
+
+#include <pygobject.h>
+#include <pygtk/pygtk.h>
+
+#include "main.h"
+#include "mainwindow.h"
+#include "summaryview.h"
+#include "quicksearch.h"
+#include "toolbar.h"
+#include "prefs_common.h"
+#include "common/tags.h"
+
+#include <glib.h>
+
+static PyObject *cm_module = NULL;
+
+PyObject* get_gobj_from_address(gpointer addr)
+{
+  GObject *obj;
+
+  if (!G_IS_OBJECT(addr))
+      return NULL;
+
+  obj = G_OBJECT(addr);
+
+  if (!obj)
+      return NULL;
+
+  return pygobject_new(obj);
+}
+
+static PyObject* private_wrap_gobj(PyObject *self, PyObject *args)
+{
+    void *addr;
+
+    if (!PyArg_ParseTuple(args, "l", &addr))
+        return NULL;
+
+    return get_gobj_from_address(addr);
+}
+
+static PyObject *get_mainwindow_action_group(PyObject *self, PyObject *args)
+{
+  MainWindow *mainwin;
+
+  mainwin =  mainwindow_get_mainwindow();
+  if(mainwin)
+    return get_gobj_from_address(mainwin->action_group);
+  else
+    return NULL;
+}
+
+static PyObject *get_mainwindow_ui_manager(PyObject *self, PyObject *args)
+{
+  MainWindow *mainwin;
+
+  mainwin =  mainwindow_get_mainwindow();
+  if(mainwin)
+    return get_gobj_from_address(mainwin->ui_manager);
+  else
+    return NULL;
+}
+
+static PyObject *get_folderview_selected_folder(PyObject *self, PyObject *args)
+{
+  MainWindow *mainwin;
+
+  mainwin =  mainwindow_get_mainwindow();
+  if(mainwin && mainwin->folderview) {
+    FolderItem *item;
+    item = folderview_get_selected_item(mainwin->folderview);
+    if(item)
+      return clawsmail_folder_new(item);
+  }
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyObject *folderview_select_folder(PyObject *self, PyObject *args)
+{
+  MainWindow *mainwin;
+
+  mainwin =  mainwindow_get_mainwindow();
+  if(mainwin && mainwin->folderview) {
+    FolderItem *item;
+    PyObject *folder;
+    folder = PyTuple_GetItem(args, 0);
+    if(!folder)
+      return NULL;
+    Py_INCREF(folder);
+    item = clawsmail_folder_get_item(folder);
+    Py_DECREF(folder);
+    if(item)
+      folderview_select(mainwin->folderview, item);
+  }
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static gboolean setup_folderitem_node(GNode *item_node, GNode *item_parent, PyObject **pyparent)
+{
+  PyObject *pynode, *children;
+  int retval, n_children, i_child;
+  PyObject *folder;
+
+  /* create a python node for the folderitem node */
+  pynode = clawsmail_node_new(cm_module);
+  if(!pynode)
+    return FALSE;
+
+  /* store Folder in pynode */
+  folder = clawsmail_folder_new(item_node->data);
+  retval = PyObject_SetAttrString(pynode, "data", folder);
+  Py_DECREF(folder);
+  if(retval == -1) {
+    Py_DECREF(pynode);
+    return FALSE;
+  }
+
+  if(pyparent && *pyparent) {
+    /* add this node to the parent's childs */
+    children = PyObject_GetAttrString(*pyparent, "children");
+    retval = PyList_Append(children, pynode);
+    Py_DECREF(children);
+
+    if(retval == -1) {
+      Py_DECREF(pynode);
+      return FALSE;
+    }
+  }
+  else if(pyparent) {
+    *pyparent = pynode;
+    Py_INCREF(pynode);
+  }
+
+  /* call this function recursively for all children of the new node */
+  n_children = g_node_n_children(item_node);
+  for(i_child = 0; i_child < n_children; i_child++) {
+    if(!setup_folderitem_node(g_node_nth_child(item_node, i_child), item_node, &pynode)) {
+      Py_DECREF(pynode);
+      return FALSE;
+    }
+  }
+
+  Py_DECREF(pynode);
+  return TRUE;
+}
+
+static PyObject* get_folder_tree_from_account_name(const char *str)
+{
+  PyObject *result;
+  GList *walk;
+
+  result = Py_BuildValue("[]");
+  if(!result)
+    return NULL;
+
+  for(walk = folder_get_list(); walk; walk = walk->next) {
+    Folder *folder = walk->data;
+    if((!str || !g_strcmp0(str, folder->name)) && folder->node) {
+      PyObject *root;
+      int n_children, i_child, retval;
+
+      /* create root nodes */
+      root = clawsmail_node_new(cm_module);
+      if(!root) {
+        Py_DECREF(result);
+        return NULL;
+      }
+
+      n_children = g_node_n_children(folder->node);
+      for(i_child = 0; i_child < n_children; i_child++) {
+        if(!setup_folderitem_node(g_node_nth_child(folder->node, i_child), folder->node, &root)) {
+          Py_DECREF(root);
+          Py_DECREF(result);
+          return NULL;
+        }
+      }
+      retval = PyList_Append(result, root);
+      Py_DECREF(root);
+      if(retval == -1) {
+        Py_DECREF(result);
+        return NULL;
+      }
+    }
+  }
+  return result;
+}
+
+static PyObject* get_folder_tree_from_folderitem(FolderItem *item)
+{
+  PyObject *result;
+  GList *walk;
+
+  for(walk = folder_get_list(); walk; walk = walk->next) {
+    Folder *folder = walk->data;
+    if(folder->node) {
+      GNode *root_node;
+
+      root_node = g_node_find(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
+      if(!root_node)
+        continue;
+
+      result = NULL;
+      if(!setup_folderitem_node(root_node, NULL, &result))
+        return NULL;
+      else
+        return result;
+    }
+  }
+
+  PyErr_SetString(PyExc_LookupError, "Folder not found");
+  return NULL;
+}
+
+static PyObject* get_folder_tree(PyObject *self, PyObject *args)
+{
+  PyObject *arg;
+  PyObject *result;
+  int retval;
+
+  Py_INCREF(Py_None);
+  arg = Py_None;
+  retval = PyArg_ParseTuple(args, "|O", &arg);
+  Py_DECREF(Py_None);
+  if(!retval)
+    return NULL;
+
+  /* calling possibilities:
+   * nothing, the mailbox name in a string, a Folder object */
+
+  /* no arguments: build up a list of folder trees */
+  if(PyTuple_Size(args) == 0) {
+    result = get_folder_tree_from_account_name(NULL);
+  }
+  else if(PyString_Check(arg)){
+    const char *str;
+    str = PyString_AsString(arg);
+    if(!str)
+      return NULL;
+
+    result = get_folder_tree_from_account_name(str);
+  }
+  else if(PyObject_TypeCheck(arg, clawsmail_folder_get_type_object())) {
+    result = get_folder_tree_from_folderitem(clawsmail_folder_get_item(arg));
+  }
+  else {
+    PyErr_SetString(PyExc_TypeError, "Parameter must be nothing, a mailbox string or a Folder object.");
+    return NULL;
+  }
+
+  return result;
+}
+
+static PyObject* quicksearch_search(PyObject *self, PyObject *args)
+{
+  const char *string;
+  int searchtype;
+  QuickSearch *qs;
+  MainWindow *mainwin;
+
+  /* must be given exactly one argument, which is a string */
+  searchtype = prefs_common.summary_quicksearch_type;
+  if(!PyArg_ParseTuple(args, "s|i", &string, &searchtype))
+    return NULL;
+
+  mainwin = mainwindow_get_mainwindow();
+  if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
+    PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
+    return NULL;
+  }
+
+  qs = mainwin->summaryview->quicksearch;
+  quicksearch_set(qs, searchtype, string);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyObject* quicksearch_clear(PyObject *self, PyObject *args)
+{
+  QuickSearch *qs;
+  MainWindow *mainwin;
+
+  mainwin = mainwindow_get_mainwindow();
+  if(!mainwin || !mainwin->summaryview || !mainwin->summaryview->quicksearch) {
+    PyErr_SetString(PyExc_LookupError, "Quicksearch not found");
+    return NULL;
+  }
+
+  qs = mainwin->summaryview->quicksearch;
+  quicksearch_set(qs, prefs_common.summary_quicksearch_type, "");
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyObject* summaryview_select_messages(PyObject *self, PyObject *args)
+{
+  PyObject *olist;
+  MainWindow *mainwin;
+  Py_ssize_t size, iEl;
+  GSList *msginfos;
+
+  mainwin = mainwindow_get_mainwindow();
+  if(!mainwin || !mainwin->summaryview) {
+    PyErr_SetString(PyExc_LookupError, "SummaryView not found");
+    return NULL;
+  }
+
+  if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &olist)) {
+    PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
+    return NULL;
+  }
+
+  msginfos = NULL;
+  size = PyList_Size(olist);
+  for(iEl = 0; iEl < size; iEl++) {
+    PyObject *element = PyList_GET_ITEM(olist, iEl);
+
+    if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
+      PyErr_SetString(PyExc_LookupError, "Argument must be a list of MessageInfo objects.");
+      return NULL;
+    }
+
+    msginfos = g_slist_prepend(msginfos, clawsmail_messageinfo_get_msginfo(element));
+  }
+
+  summary_unselect_all(mainwin->summaryview);
+  summary_select_by_msg_list(mainwin->summaryview, msginfos);
+  g_slist_free(msginfos);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyObject* get_summaryview_selected_message_list(PyObject *self, PyObject *args)
+{
+  MainWindow *mainwin;
+  GSList *list, *walk;
+  PyObject *result;
+
+  mainwin = mainwindow_get_mainwindow();
+  if(!mainwin || !mainwin->summaryview) {
+    PyErr_SetString(PyExc_LookupError, "SummaryView not found");
+    return NULL;
+  }
+
+  result = Py_BuildValue("[]");
+  if(!result)
+    return NULL;
+
+  list = summary_get_selected_msg_list(mainwin->summaryview);
+  for(walk = list; walk; walk = walk->next) {
+    PyObject *msg;
+    msg = clawsmail_messageinfo_new(walk->data);
+    if(PyList_Append(result, msg) == -1) {
+      Py_DECREF(result);
+      return NULL;
+    }
+  }
+  g_slist_free(list);
+
+  return result;
+}
+
+static PyObject* is_exiting(PyObject *self, PyObject *args)
+{
+  if(claws_is_exiting())
+    Py_RETURN_TRUE;
+  else
+    Py_RETURN_FALSE;
+}
+
+static PyObject* get_tags(PyObject *self, PyObject *args)
+{
+  Py_ssize_t num_tags;
+  PyObject *tags_tuple;
+  GSList *tags_list;
+
+  tags_list = tags_get_list();
+  num_tags = g_slist_length(tags_list);
+
+  tags_tuple = PyTuple_New(num_tags);
+  if(tags_tuple != NULL) {
+    Py_ssize_t iTag;
+    PyObject *tag_object;
+    GSList *walk;
+
+    iTag = 0;
+    for(walk = tags_list; walk; walk = walk->next) {
+      tag_object = Py_BuildValue("s", tags_get_tag(GPOINTER_TO_INT(walk->data)));
+      if(tag_object == NULL) {
+        Py_DECREF(tags_tuple);
+        return NULL;
+      }
+      PyTuple_SET_ITEM(tags_tuple, iTag++, tag_object);
+    }
+  }
+
+  g_slist_free(tags_list);
+
+  return tags_tuple;
+}
+
+static PyObject* make_sure_tag_exists(PyObject *self, PyObject *args)
+{
+  int retval;
+  const char *tag_str;
+
+  retval = PyArg_ParseTuple(args, "s", &tag_str);
+  if(!retval)
+    return NULL;
+
+  if(IS_NOT_RESERVED_TAG(tag_str) == FALSE) {
+    /* tag name is reserved, raise KeyError */
+    PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
+    return NULL;
+  }
+
+  tags_add_tag(tag_str);
+
+  Py_RETURN_NONE;
+}
+
+static PyObject* delete_tag(PyObject *self, PyObject *args)
+{
+  int retval;
+  const char *tag_str;
+  gint tag_id;
+  MainWindow *mainwin;
+
+  retval = PyArg_ParseTuple(args, "s", &tag_str);
+  if(!retval)
+    return NULL;
+
+  tag_id = tags_get_id_for_str(tag_str);
+  if(tag_id == -1) {
+    PyErr_SetString(PyExc_KeyError, "Tag does not exist");
+    return NULL;
+  }
+
+  tags_remove_tag(tag_id);
+
+  /* update display */
+  mainwin = mainwindow_get_mainwindow();
+  if(mainwin)
+    summary_redisplay_msg(mainwin->summaryview);
+
+  Py_RETURN_NONE;
+}
+
+
+static PyObject* rename_tag(PyObject *self, PyObject *args)
+{
+  int retval;
+  const char *old_tag_str;
+  const char *new_tag_str;
+  gint tag_id;
+  MainWindow *mainwin;
+
+  retval = PyArg_ParseTuple(args, "ss", &old_tag_str, &new_tag_str);
+  if(!retval)
+    return NULL;
+
+  if((IS_NOT_RESERVED_TAG(new_tag_str) == FALSE) ||(IS_NOT_RESERVED_TAG(old_tag_str) == FALSE)) {
+    PyErr_SetString(PyExc_ValueError, "Tag name is reserved");
+    return NULL;
+  }
+
+  tag_id = tags_get_id_for_str(old_tag_str);
+  if(tag_id == -1) {
+    PyErr_SetString(PyExc_KeyError, "Tag does not exist");
+    return NULL;
+  }
+
+  tags_update_tag(tag_id, new_tag_str);
+
+  /* update display */
+  mainwin = mainwindow_get_mainwindow();
+  if(mainwin)
+    summary_redisplay_msg(mainwin->summaryview);
+
+  Py_RETURN_NONE;
+}
+
+static gboolean get_message_list_for_move_or_copy(PyObject *messagelist, PyObject *folder, GSList **list)
+{
+  Py_ssize_t size, iEl;
+  FolderItem *folderitem;
+  
+  *list = NULL;
+
+  folderitem = clawsmail_folder_get_item(folder);
+  if(!folderitem) {
+    PyErr_SetString(PyExc_LookupError, "Brokern Folder object.");
+    return FALSE;
+  }
+
+  size = PyList_Size(messagelist);
+  for(iEl = 0; iEl < size; iEl++) {
+    PyObject *element = PyList_GET_ITEM(messagelist, iEl);
+    MsgInfo *msginfo;
+
+    if(!element || !PyObject_TypeCheck(element, clawsmail_messageinfo_get_type_object())) {
+      PyErr_SetString(PyExc_TypeError, "Argument must be a list of MessageInfo objects.");
+      return FALSE;
+    }
+    
+    msginfo = clawsmail_messageinfo_get_msginfo(element);
+    if(!msginfo) {
+      PyErr_SetString(PyExc_LookupError, "Broken MessageInfo object.");
+      return FALSE;
+    }
+   
+    procmsg_msginfo_set_to_folder(msginfo, folderitem);
+    *list = g_slist_prepend(*list, msginfo);
+  }
+  return TRUE;
+}
+
+static PyObject* move_or_copy_messages(PyObject *self, PyObject *args, gboolean move)
+{
+  PyObject *messagelist;
+  PyObject *folder;
+  int retval;
+  GSList *list = NULL;
+  
+  retval = PyArg_ParseTuple(args, "O!O!",
+    &PyList_Type, &messagelist,
+    clawsmail_folder_get_type_object(), &folder);
+  if(!retval )
+    return NULL;  
+
+  folder_item_update_freeze();
+  
+  if(!get_message_list_for_move_or_copy(messagelist, folder, &list))
+    goto err;
+  
+  if(move)   
+    procmsg_move_messages(list);
+  else
+    procmsg_copy_messages(list);
+      
+  folder_item_update_thaw();
+  g_slist_free(list);
+  Py_RETURN_NONE;
+
+err:
+  folder_item_update_thaw();
+  g_slist_free(list);
+  return NULL;
+}
+
+static PyObject* move_messages(PyObject *self, PyObject *args)
+{
+  return move_or_copy_messages(self, args, TRUE);
+}
+
+
+static PyObject* copy_messages(PyObject *self, PyObject *args)
+{
+  return move_or_copy_messages(self, args, FALSE);
+}
+
+static PyMethodDef ClawsMailMethods[] = {
+    /* public */
+    {"get_mainwindow_action_group",  get_mainwindow_action_group, METH_NOARGS,
+     "get_mainwindow_action_group() - get action group of main window menu\n"
+     "\n"
+     "Returns the gtk.ActionGroup for the main window."},
+
+    {"get_mainwindow_ui_manager",  get_mainwindow_ui_manager, METH_NOARGS, 
+     "get_mainwindow_ui_manager() - get ui manager of main window\n"
+     "\n"
+     "Returns the gtk.UIManager for the main window."},
+
+    {"get_folder_tree",  get_folder_tree, METH_VARARGS,
+     "get_folder_tree([root]) - get a folder tree\n"
+     "\n"
+     "Without arguments, get a list of folder trees for all mailboxes.\n"
+     "\n"
+     "If the optional root argument is a string, it is supposed to be a\n"
+     "mailbox name. The function then returns a tree of folders of that mailbox.\n"
+     "\n"
+     "If the optional root argument is a clawsmail.Folder, the function\n"
+     "returns a tree of subfolders with the given folder as root element.\n"
+     "\n"
+     "In any case, a tree consists of elements of the type clawsmail.Node."},
+
+    {"get_folderview_selected_folder",  get_folderview_selected_folder, METH_NOARGS,
+     "get_folderview_selected_folder() - get selected folder in folderview\n"
+     "\n"
+     "Returns the currently selected folder as a clawsmail.Folder."},
+    {"folderview_select_folder",  folderview_select_folder, METH_VARARGS,
+     "folderview_select_folder(folder) - select folder in folderview\n"
+     "\n"
+     "Takes an argument of type clawsmail.Folder, and selects the corresponding folder."},
+
+    {"quicksearch_search", quicksearch_search, METH_VARARGS,
+     "quicksearch_search(string [, type]) - perform a quicksearch\n"
+     "\n"
+     "Perform a quicksearch of the given string. The optional type argument can be\n"
+     "one of clawsmail.QUICK_SEARCH_SUBJECT, clawsmail.QUICK_SEARCH_FROM, clawsmail.QUICK_SEARCH_TO,\n"
+     "clawsmail.QUICK_SEARCH_EXTENDED, clawsmail.QUICK_SEARCH_MIXED, or clawsmail.QUICK_SEARCH_TAG.\n"
+     "If it is omitted, the current selection is used. The string argument has to be a valid search\n"
+     "string corresponding to the type."},
+
+    {"quicksearch_clear", quicksearch_clear, METH_NOARGS,
+     "quicksearch_clear() - clear the quicksearch"},
+
+    {"get_summaryview_selected_message_list", get_summaryview_selected_message_list, METH_NOARGS,
+     "get_summaryview_selected_message_list() - get selected message list\n"
+     "\n"
+     "Get a list of clawsmail.MessageInfo objects of the current selection."},
+
+    {"summaryview_select_messages", summaryview_select_messages, METH_VARARGS,
+     "summaryview_select_messages(message_list) - select a list of messages in the summary view\n"
+     "\n"
+     "Select a list of clawsmail.MessageInfo objects in the summary view."},
+
+    {"is_exiting", is_exiting, METH_NOARGS,
+     "is_exiting() - test whether Claws Mail is currently exiting\n"
+     "\n"
+     "Returns True if Claws Mail is currently exiting. The most common usage for this is to skip\n"
+     "unnecessary cleanup tasks in a shutdown script when Claws Mail is exiting anyways. If the Python\n"
+     "plugin is explicitly unloaded, the shutdown script will still be called, but this function will\n"
+     "return False."},
+
+    {"move_messages", move_messages, METH_VARARGS,
+     "move_messages(message_list, target_folder) - move a list of messages to a target folder\n"
+     "\n"
+     "Move a list of clawsmail.MessageInfo objects to a target folder.\n"
+     "The target_folder argument has to be a clawsmail.Folder object."},
+
+    {"copy_messages", copy_messages, METH_VARARGS,
+     "copy_messages(message_list, target_folder) - copy a list of messages to a target folder\n"
+     "\n"
+     "Copy a list of clawsmail.MessageInfo objects to a target folder.\n"
+     "The target_folder argument has to be a clawsmail.Folder object."},
+
+    {"get_tags", get_tags, METH_NOARGS,
+     "get_tags() - get a tuple of all tags that Claws Mail knows about\n"
+     "\n"
+     "Get a tuple of strings representing all tags that are defined in Claws Mail."},
+
+    {"make_sure_tag_exists", make_sure_tag_exists, METH_VARARGS,
+     "make_sure_tag_exists(tag) - make sure that the specified tag exists\n"
+     "\n"
+     "This function creates the given tag if it does not exist yet.\n"
+     "It is not an error if the tag already exists. In this case, this function does nothing.\n"
+     "However, if a reserved tag name is chosen, a ValueError exception is raised."},
+
+    {"delete_tag", delete_tag, METH_VARARGS,
+     "delete_tag(tag) - delete a tag\n"
+     "\n"
+     "This function deletes an existing tag.\n"
+     "Raises a KeyError exception if the tag does not exist."},
+
+    {"rename_tag", rename_tag, METH_VARARGS,
+     "rename_tag(old_tag, new_tag) - rename tag old_tag to new_tag\n"
+     "\n"
+     "This function renames an existing tag.\n"
+     "Raises a KeyError exception if the tag does not exist.\n"
+     "Raises a ValueError exception if the old or new tag name is a reserved name."},
+
+     /* private */
+    {"__gobj", private_wrap_gobj, METH_VARARGS,
+     "__gobj(ptr) - transforms a C GObject pointer into a PyGObject\n"
+     "\n"
+     "For internal usage only."},
+
+    {NULL, NULL, 0, NULL}
+};
+
+static void initmiscstuff(PyObject *module)
+{
+  PyObject *dict;
+  PyObject *res;
+  const char *cmd =
+      "QUICK_SEARCH_SUBJECT = 0\n"
+      "QUICK_SEARCH_FROM = 1\n"
+      "QUICK_SEARCH_TO = 2\n"
+      "QUICK_SEARCH_EXTENDED = 3\n"
+      "QUICK_SEARCH_MIXED = 4\n"
+      "QUICK_SEARCH_TAG = 5\n"
+      "\n";
+  dict = PyModule_GetDict(module);
+  res = PyRun_String(cmd, Py_file_input, dict, dict);
+  Py_XDECREF(res);
+}
+
+
+void claws_mail_python_init(void)
+{
+  if (!Py_IsInitialized())
+      Py_Initialize();
+
+  /* create module */
+  cm_module = Py_InitModule3("clawsmail", ClawsMailMethods,
+      "This module can be used to access some of Claws Mail's data structures\n"
+      "in order to extend or modify the user interface or automate repetitive tasks.\n"
+      "\n"
+      "Whenever possible, the interface works with standard GTK+ widgets\n"
+      "via the PyGTK bindings, so you can refer to the GTK+ / PyGTK documentation\n"
+      "to find out about all possible options.\n"
+      "\n"
+      "The interface to Claws Mail in this module is extended on a 'as-needed' basis.\n"
+      "If you're missing something specific, try contacting the author.");
+
+  /* initialize classes */
+  initnode(cm_module);
+  initcomposewindow(cm_module);
+  initfolder(cm_module);
+  initmessageinfo(cm_module);
+
+  /* initialize misc things */
+  initmiscstuff(cm_module);
+
+  PyRun_SimpleString("import clawsmail\n");
+  PyRun_SimpleString("clawsmail.compose_window = None\n");
+}
+
+
+void put_composewindow_into_module(Compose *compose)
+{
+  PyObject *pycompose;
+
+  pycompose = clawsmail_compose_new(cm_module, compose);
+  PyObject_SetAttrString(cm_module, "compose_window", pycompose);
+  Py_DECREF(pycompose);
+}
diff --git a/src/plugins/python/clawsmailmodule.h b/src/plugins/python/clawsmailmodule.h
new file mode 100644 (file)
index 0000000..21846a6
--- /dev/null
@@ -0,0 +1,33 @@
+/* Python plugin for Claws-Mail
+ * Copyright (C) 2009 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef CLAWSMAILMODULE_H
+#define CLAWSMAILMODULE_H
+
+#include <Python.h>
+#include <glib.h>
+
+#include "compose.h"
+
+
+void claws_mail_python_init(void);
+
+PyObject* get_gobj_from_address(gpointer addr);
+void put_composewindow_into_module(Compose *compose);
+
+#endif /* CLAWSMAILMODULE_H */
diff --git a/src/plugins/python/composewindowtype.c b/src/plugins/python/composewindowtype.c
new file mode 100644 (file)
index 0000000..59ebeea
--- /dev/null
@@ -0,0 +1,617 @@
+/* Python plugin for Claws-Mail
+ * Copyright (C) 2009 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "composewindowtype.h"
+
+#include "clawsmailmodule.h"
+#include "foldertype.h"
+#include "messageinfotype.h"
+
+#include "mainwindow.h"
+#include "account.h"
+#include "summaryview.h"
+
+#include <structmember.h>
+
+#include <string.h>
+
+typedef struct {
+    PyObject_HEAD
+    PyObject *ui_manager;
+    PyObject *text;
+    PyObject *replyinfo;
+    Compose *compose;
+} clawsmail_ComposeWindowObject;
+
+static void ComposeWindow_dealloc(clawsmail_ComposeWindowObject* self)
+{
+  Py_XDECREF(self->ui_manager);
+  Py_XDECREF(self->text);
+  Py_XDECREF(self->replyinfo);
+  self->ob_type->tp_free((PyObject*)self);
+}
+
+static void flush_gtk_queue(void)
+{
+  while(gtk_events_pending())
+    gtk_main_iteration();
+}
+
+static void store_py_object(PyObject **target, PyObject *obj)
+{
+  Py_XDECREF(*target);
+  if(obj)
+  {
+    Py_INCREF(obj);
+    *target = obj;
+  }
+  else {
+    Py_INCREF(Py_None);
+    *target = Py_None;
+  }
+}
+
+static void composewindow_set_compose(clawsmail_ComposeWindowObject *self, Compose *compose)
+{
+  self->compose = compose;
+
+  store_py_object(&(self->ui_manager), get_gobj_from_address(compose->ui_manager));
+  store_py_object(&(self->text), get_gobj_from_address(compose->text));
+
+  store_py_object(&(self->replyinfo), clawsmail_messageinfo_new(compose->replyinfo));
+}
+
+static int ComposeWindow_init(clawsmail_ComposeWindowObject *self, PyObject *args, PyObject *kwds)
+{
+  MainWindow *mainwin;
+  PrefsAccount *ac = NULL;
+  FolderItem *item;
+  GList* list;
+  GList* cur;
+  gboolean did_find_compose;
+  Compose *compose = NULL;
+  const char *ss;
+  unsigned char open_window;
+  /* if __open_window is set to 0/False,
+   * composewindow_set_compose must be called before this object is valid */
+  static char *kwlist[] = {"address", "__open_window", NULL};
+
+  ss = NULL;
+  open_window = 1;
+  PyArg_ParseTupleAndKeywords(args, kwds, "|sb", kwlist, &ss, &open_window);
+
+  if(open_window) {
+    mainwin = mainwindow_get_mainwindow();
+    item = mainwin->summaryview->folder_item;
+    did_find_compose = FALSE;
+
+    if(ss) {
+      ac = account_find_from_address(ss, FALSE);
+      if (ac && ac->protocol != A_NNTP) {
+        compose = compose_new_with_folderitem(ac, item, NULL);
+        did_find_compose = TRUE;
+      }
+    }
+    if(!did_find_compose) {
+      if (item) {
+        ac = account_find_from_item(item);
+        if (ac && ac->protocol != A_NNTP) {
+          compose = compose_new_with_folderitem(ac, item, NULL);
+          did_find_compose = TRUE;
+        }
+      }
+
+      /* use current account */
+      if (!did_find_compose && cur_account && (cur_account->protocol != A_NNTP)) {
+        compose = compose_new_with_folderitem(cur_account, item, NULL);
+        did_find_compose = TRUE;
+      }
+
+      if(!did_find_compose) {
+        /* just get the first one */
+        list = account_get_list();
+        for (cur = list ; cur != NULL ; cur = g_list_next(cur)) {
+          ac = (PrefsAccount *) cur->data;
+          if (ac->protocol != A_NNTP) {
+            compose = compose_new_with_folderitem(ac, item, NULL);
+            did_find_compose = TRUE;
+          }
+        }
+      }
+    }
+
+    if(!did_find_compose)
+      return -1;
+
+    composewindow_set_compose(self, compose);
+    gtk_widget_show_all(compose->window);
+    flush_gtk_queue();
+  }
+  return 0;
+}
+
+/* this is here because wrapping GTK_EDITABLEs in PyGTK is buggy */
+static PyObject* get_python_object_from_gtk_entry(GtkWidget *entry)
+{
+  return Py_BuildValue("s", gtk_entry_get_text(GTK_ENTRY(entry)));
+}
+
+static PyObject* set_gtk_entry_from_python_object(GtkWidget *entry, PyObject *args)
+{
+  const char *ss;
+
+  if(!PyArg_ParseTuple(args, "s", &ss))
+    return NULL;
+
+  gtk_entry_set_text(GTK_ENTRY(entry), ss);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyObject* ComposeWindow_get_subject(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  return get_python_object_from_gtk_entry(self->compose->subject_entry);
+}
+
+static PyObject* ComposeWindow_set_subject(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  PyObject *ret;
+  ret = set_gtk_entry_from_python_object(self->compose->subject_entry, args);
+  flush_gtk_queue();
+  return ret;
+}
+
+static PyObject* ComposeWindow_get_from(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  return get_python_object_from_gtk_entry(self->compose->from_name);
+}
+
+static PyObject* ComposeWindow_set_from(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  PyObject *ret;
+  ret = set_gtk_entry_from_python_object(self->compose->from_name, args);
+  flush_gtk_queue();
+  return ret;
+}
+
+static PyObject* ComposeWindow_add_To(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  const char *ss;
+
+  if(!PyArg_ParseTuple(args, "s", &ss))
+    return NULL;
+
+  compose_entry_append(self->compose, ss, COMPOSE_TO, PREF_NONE);
+
+  flush_gtk_queue();
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyObject* ComposeWindow_add_Cc(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  const char *ss;
+
+  if(!PyArg_ParseTuple(args, "s", &ss))
+    return NULL;
+
+  compose_entry_append(self->compose, ss, COMPOSE_CC, PREF_NONE);
+
+  flush_gtk_queue();
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyObject* ComposeWindow_add_Bcc(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  const char *ss;
+
+  if(!PyArg_ParseTuple(args, "s", &ss))
+    return NULL;
+
+  compose_entry_append(self->compose, ss, COMPOSE_BCC, PREF_NONE);
+
+  flush_gtk_queue();
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyObject* ComposeWindow_attach(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  PyObject *olist;
+  Py_ssize_t size, iEl;
+  GList *list = NULL;
+
+  if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &olist))
+    return NULL;
+
+  size = PyList_Size(olist);
+  for(iEl = 0; iEl < size; iEl++) {
+    char *ss;
+    PyObject *element = PyList_GET_ITEM(olist, iEl);
+
+    if(!element)
+      continue;
+
+    Py_INCREF(element);
+    if(!PyArg_Parse(element, "s", &ss)) {
+      Py_DECREF(element);
+      if(list)
+        g_list_free(list);
+      return NULL;
+    }
+    list = g_list_prepend(list, ss);
+    Py_DECREF(element);
+  }
+
+  compose_attach_from_list(self->compose, list, FALSE);
+  g_list_free(list);
+
+  flush_gtk_queue();
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyObject* ComposeWindow_get_header_list(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  GSList *walk;
+  PyObject *retval;
+
+  retval = Py_BuildValue("[]");
+  for(walk = self->compose->header_list; walk; walk = walk->next) {
+    ComposeHeaderEntry *headerentry = walk->data;
+    const gchar *header;
+    const gchar *text;
+
+    header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(headerentry->combo))), 0, -1);
+    text = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
+
+    if(text && strcmp("", text)) {
+      PyObject *ee;
+      int ok;
+
+      ee = Py_BuildValue("(ss)", header, text);
+      ok = PyList_Append(retval, ee);
+      Py_DECREF(ee);
+      if(ok == -1) {
+        Py_DECREF(retval);
+        return NULL;
+      }
+    }
+  }
+  return retval;
+}
+
+static PyObject* ComposeWindow_add_header(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  const char *header;
+  const char *text;
+  gint num;
+
+  if(!PyArg_ParseTuple(args, "ss", &header, &text))
+    return NULL;
+
+  /* add a dummy, and modify it then */
+  compose_entry_append(self->compose, "dummy1dummy2dummy3", COMPOSE_TO, PREF_NONE);
+  num = g_slist_length(self->compose->header_list);
+  if(num > 1) {
+    ComposeHeaderEntry *headerentry;
+    headerentry = g_slist_nth_data(self->compose->header_list, num-2);
+    if(headerentry) {
+      GtkEditable *editable;
+      gint pos;
+      gtk_entry_set_text(GTK_ENTRY(headerentry->entry), text);
+      editable = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(headerentry->combo)));
+      gtk_editable_delete_text(editable, 0, -1);
+      gtk_editable_insert_text(editable, header, -1, &pos);
+    }
+  }
+
+  flush_gtk_queue();
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+/* this is pretty ugly, as the compose struct does not maintain a pointer to the account selection combo */
+static PyObject* ComposeWindow_get_account_selection(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  GList *children, *walk;
+
+  children = gtk_container_get_children(GTK_CONTAINER(self->compose->header_table));
+  for(walk = children; walk; walk = walk->next) {
+    if(GTK_IS_HBOX(walk->data)) {
+      GList *children2, *walk2;
+      children2 = gtk_container_get_children(GTK_CONTAINER(walk->data));
+      for(walk2 = children2; walk2; walk2 = walk2->next) {
+        if(GTK_IS_EVENT_BOX(walk2->data)) {
+          return get_gobj_from_address(gtk_container_get_children(GTK_CONTAINER(walk2->data))->data);
+        }
+      }
+    }
+  }
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyObject* ComposeWindow_save_message_to(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  PyObject *arg;
+
+  if(!PyArg_ParseTuple(args, "O", &arg))
+    return NULL;
+
+  if(PyString_Check(arg)) {
+    GtkEditable *editable;
+    gint pos;
+
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->compose->savemsg_checkbtn), TRUE);
+
+    editable = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(self->compose->savemsg_combo)));
+    gtk_editable_delete_text(editable, 0, -1);
+    gtk_editable_insert_text(editable, PyString_AsString(arg), -1, &pos);
+  }
+  else if(clawsmail_folder_check(arg)) {
+    GtkEditable *editable;
+    gint pos;
+
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->compose->savemsg_checkbtn), TRUE);
+
+    editable = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(self->compose->savemsg_combo)));
+    gtk_editable_delete_text(editable, 0, -1);
+    gtk_editable_insert_text(editable, folder_item_get_identifier(clawsmail_folder_get_item(arg)), -1, &pos);
+  }
+  else if (arg == Py_None){
+    /* turn off checkbutton */
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->compose->savemsg_checkbtn), FALSE);
+  }
+  else {
+    PyErr_SetString(PyExc_TypeError, "function takes exactly one argument which may be a folder object, a string, or None");
+    return NULL;
+  }
+
+  flush_gtk_queue();
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyObject* ComposeWindow_set_modified(clawsmail_ComposeWindowObject *self, PyObject *args)
+{
+  char modified = 0;
+  gboolean old_modified;
+
+  if(!PyArg_ParseTuple(args, "b", &modified))
+    return NULL;
+
+  old_modified = self->compose->modified;
+
+  self->compose->modified = (modified != 0);
+
+  /* If the modified state changed, rewrite window title.
+   * This partly duplicates functionality in compose.c::compose_set_title().
+   * While it's nice to not have to modify Claws Mail for this to work,
+   * it would be cleaner to export that function in Claws Mail. */
+  if((strcmp(gtk_window_get_title(GTK_WINDOW(self->compose->window)), _("Compose message")) != 0) &&
+      (old_modified != self->compose->modified)) {
+      gchar *str;
+      gchar *edited;
+      gchar *subject;
+
+      edited = self->compose->modified  ? _(" [Edited]") : "";
+      subject = gtk_editable_get_chars(GTK_EDITABLE(self->compose->subject_entry), 0, -1);
+      if(subject && strlen(subject))
+        str = g_strdup_printf(_("%s - Compose message%s"),
+            subject, edited);
+      else
+        str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
+      gtk_window_set_title(GTK_WINDOW(self->compose->window), str);
+      g_free(str);
+      g_free(subject);
+  }
+
+  flush_gtk_queue();
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static PyMethodDef ComposeWindow_methods[] = {
+    {"set_subject", (PyCFunction)ComposeWindow_set_subject, METH_VARARGS,
+     "set_subject(text) - set subject to text\n"
+     "\n"
+     "Set the subject to text. text must be a string."},
+
+    {"get_subject", (PyCFunction)ComposeWindow_get_subject, METH_NOARGS,
+     "get_subject() - get subject\n"
+     "\n"
+     "Get a string of the current subject entry."},
+
+    {"set_from", (PyCFunction)ComposeWindow_set_from, METH_VARARGS,
+     "set_from(text) - set From header entry to text\n"
+     "\n"
+     "Set the From header entry to text. text must be a string.\n"
+     "Beware: No sanity checking is performed."},
+
+    {"get_from", (PyCFunction)ComposeWindow_get_from, METH_NOARGS,
+     "get_from - get From header entry\n"
+     "\n"
+     "Get a string of the current From header entry."},
+
+    {"add_To",  (PyCFunction)ComposeWindow_add_To,  METH_VARARGS,
+     "add_To(text) - append another To header with text\n"
+     "\n"
+     "Add another header line with the combo box set to To:, and the\n"
+     "content set to text."},
+
+    {"add_Cc",  (PyCFunction)ComposeWindow_add_Cc,  METH_VARARGS,
+     "add_Cc(text) - append another Cc header with text\n"
+     "\n"
+     "Add another header line with the combo box set to Cc:, and the\n"
+     "content set to text."},
+
+    {"add_Bcc", (PyCFunction)ComposeWindow_add_Bcc, METH_VARARGS,
+     "add_Bcc(text) - append another Bcc header with text\n"
+     "\n"
+     "Add another header line with the combo box set to Bcc:, and the\n"
+     "content set to text."},
+
+    {"add_header", (PyCFunction)ComposeWindow_add_header, METH_VARARGS,
+     "add_header(headername, text) - add a custom header\n"
+     "\n"
+     "Adds a custom header with the header set to headername, and the\n"
+     "contents set to text."},
+
+    {"get_header_list", (PyCFunction)ComposeWindow_get_header_list, METH_NOARGS,
+     "get_header_list() - get list of headers\n"
+     "\n"
+     "Gets a list of headers that are currently defined in the compose window.\n"
+     "The return value is a list of tuples, where the first tuple element is\n"
+     "the header name (entry in the combo box) and the second element is the contents."},
+
+    {"attach",  (PyCFunction)ComposeWindow_attach, METH_VARARGS,
+     "attach(filenames) - attach a list of files\n"
+     "\n"
+     "Attach files to the mail. The filenames argument is a list of\n"
+     "string of the filenames that are being attached."},
+
+    {"get_account_selection", (PyCFunction)ComposeWindow_get_account_selection, METH_NOARGS,
+     "get_account_selection() - get account selection widget\n"
+     "\n"
+     "Returns the account selection combo box as a gtk.ComboBox"},
+
+    {"save_message_to", (PyCFunction)ComposeWindow_save_message_to, METH_VARARGS,
+     "save_message_to(folder) - save message to folder id\n"
+     "\n"
+     "Set the folder where the sent message will be saved to. folder may be\n"
+     "a Folder, a string of the folder identifier (e.g. #mh/foo/bar), or\n"
+     "None is which case the message will not be saved at all."},
+
+     {"set_modified", (PyCFunction)ComposeWindow_set_modified, METH_VARARGS,
+     "set_modified(bool) - set or unset modification marker of compose window\n"
+     "\n"
+     "Set or unset the modification marker of the compose window. This marker determines\n"
+     "for example whether you get a confirmation dialog when closing the compose window\n"
+     "or not.\n"
+     "In the usual case, Claws Mail keeps track of the modification status itself.\n"
+     "However, there are cases when it might be desirable to overwrite the marker,\n"
+     "for example because a compose_any script modifies the body or subject which\n"
+     "can be regarded compose window preprocessing and should not trigger a confirmation\n"
+     "dialog on close like a manual edit."},
+
+    {NULL}
+};
+
+static PyMemberDef ComposeWindow_members[] = {
+    {"ui_manager", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, ui_manager), 0,
+     "ui_manager - the gtk.UIManager of the compose window"},
+
+    {"text", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, text), 0,
+     "text - the gtk.TextView widget of the message body"},
+
+    {"replyinfo", T_OBJECT_EX, offsetof(clawsmail_ComposeWindowObject, replyinfo), 0,
+     "replyinfo - The MessageInfo object of the message that is being replied to, or None"},
+
+    {NULL}
+};
+
+static PyTypeObject clawsmail_ComposeWindowType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                         /*ob_size*/
+    "clawsmail.ComposeWindow", /*tp_name*/
+    sizeof(clawsmail_ComposeWindowObject), /*tp_basicsize*/
+    0,                         /*tp_itemsize*/
+    (destructor)ComposeWindow_dealloc, /*tp_dealloc*/
+    0,                         /*tp_print*/
+    0,                         /*tp_getattr*/
+    0,                         /*tp_setattr*/
+    0,                         /*tp_compare*/
+    0,                         /*tp_repr*/
+    0,                         /*tp_as_number*/
+    0,                         /*tp_as_sequence*/
+    0,                         /*tp_as_mapping*/
+    0,                         /*tp_hash */
+    0,                         /*tp_call*/
+    0,                         /*tp_str*/
+    0,                         /*tp_getattro*/
+    0,                         /*tp_setattro*/
+    0,                         /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT,        /*tp_flags*/
+    /* tp_doc */
+    "ComposeWindow objects. Optional argument to constructor: sender account address. ",
+    0,                         /* tp_traverse */
+    0,                         /* tp_clear */
+    0,                         /* tp_richcompare */
+    0,                         /* tp_weaklistoffset */
+    0,                         /* tp_iter */
+    0,                         /* tp_iternext */
+    ComposeWindow_methods,     /* tp_methods */
+    ComposeWindow_members,     /* tp_members */
+    0,                         /* tp_getset */
+    0,                         /* tp_base */
+    0,                         /* tp_dict */
+    0,                         /* tp_descr_get */
+    0,                         /* tp_descr_set */
+    0,                         /* tp_dictoffset */
+    (initproc)ComposeWindow_init, /* tp_init */
+    0,                         /* tp_alloc */
+    0,                         /* tp_new */
+};
+
+PyMODINIT_FUNC initcomposewindow(PyObject *module)
+{
+    clawsmail_ComposeWindowType.tp_new = PyType_GenericNew;
+    if(PyType_Ready(&clawsmail_ComposeWindowType) < 0)
+        return;
+
+    Py_INCREF(&clawsmail_ComposeWindowType);
+    PyModule_AddObject(module, "ComposeWindow", (PyObject*)&clawsmail_ComposeWindowType);
+}
+
+PyObject* clawsmail_compose_new(PyObject *module, Compose *compose)
+{
+  PyObject *class, *dict;
+  PyObject *self, *args, *kw;
+
+  if(!compose) {
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+
+  dict = PyModule_GetDict(module);
+  class = PyDict_GetItemString(dict, "ComposeWindow");
+  args = Py_BuildValue("()");
+  kw = Py_BuildValue("{s:b}", "__open_window", 0);
+  self = PyObject_Call(class, args, kw);
+  Py_DECREF(args);
+  Py_DECREF(kw);
+  composewindow_set_compose((clawsmail_ComposeWindowObject*)self, compose);
+  return self;
+}
diff --git a/src/plugins/python/composewindowtype.h b/src/plugins/python/composewindowtype.h
new file mode 100644 (file)
index 0000000..4d7b422
--- /dev/null
@@ -0,0 +1,33 @@
+/* Python plugin for Claws-Mail
+ * Copyright (C) 2009 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPOSEWINDOWTYPE_H
+#define COMPOSEWINDOWTYPE_H
+
+#include <Python.h>
+
+#include "compose.h"
+
+#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
+#define PyMODINIT_FUNC void
+#endif
+
+PyMODINIT_FUNC initcomposewindow(PyObject *module);
+
+PyObject* clawsmail_compose_new(PyObject *module, Compose *compose);
+
+#endif /* COMPOSEWINDOWTYPE_H */
diff --git a/src/plugins/python/foldertype.c b/src/plugins/python/foldertype.c
new file mode 100644 (file)
index 0000000..3d36223
--- /dev/null
@@ -0,0 +1,271 @@
+/* Python plugin for Claws-Mail
+ * Copyright (C) 2009 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "foldertype.h"
+#include "messageinfotype.h"
+
+#include <structmember.h>
+
+
+typedef struct {
+    PyObject_HEAD
+    PyObject *name;
+    PyObject *path;
+    PyObject *mailbox_name;
+    FolderItem *folderitem;
+} clawsmail_FolderObject;
+
+
+static void Folder_dealloc(clawsmail_FolderObject* self)
+{
+  Py_XDECREF(self->name);
+  Py_XDECREF(self->path);
+  Py_XDECREF(self->mailbox_name);
+  self->ob_type->tp_free((PyObject*)self);
+}
+
+#define FOLDERITEM_STRING_TO_PYTHON_FOLDER_MEMBER(self,fis, pms)    \
+  do {                                                              \
+    if(fis) {                                                       \
+      PyObject *str;                                                \
+      str = PyString_FromString(fis);                               \
+      if(str) {                                                     \
+        int retval;                                                 \
+        retval = PyObject_SetAttrString((PyObject*)self, pms, str); \
+        Py_DECREF(str);                                             \
+        if(retval == -1)                                            \
+          goto err;                                                 \
+      }                                                             \
+    }                                                               \
+  } while(0)
+
+static int Folder_init(clawsmail_FolderObject *self, PyObject *args, PyObject *kwds)
+{
+  const char *ss = NULL;
+  FolderItem *folderitem = NULL;
+  char create = 0;
+
+  /* optional constructor argument: folderitem id string */
+  if(!PyArg_ParseTuple(args, "|sb", &ss, &create))
+    return -1;
+  
+  Py_INCREF(Py_None);
+  self->name = Py_None;
+  
+  Py_INCREF(Py_None);
+  self->path = Py_None;
+
+  Py_INCREF(Py_None);
+  self->mailbox_name = Py_None;
+
+  if(ss) {
+    if(create == 0) {
+      folderitem = folder_find_item_from_identifier(ss);
+      if(!folderitem) {
+        PyErr_SetString(PyExc_ValueError, "A folder with that path does not exist, and the create parameter was False.");
+        return -1;
+      }
+    }
+    else {
+      folderitem = folder_get_item_from_identifier(ss);
+      if(!folderitem) {
+        PyErr_SetString(PyExc_IOError, "A folder with that path does not exist, and could not be created.");
+        return -1;
+      }
+    }
+  }
+
+  if(folderitem) {
+    FOLDERITEM_STRING_TO_PYTHON_FOLDER_MEMBER(self, folderitem->name, "name");
+    FOLDERITEM_STRING_TO_PYTHON_FOLDER_MEMBER(self, folderitem->path, "path");
+    FOLDERITEM_STRING_TO_PYTHON_FOLDER_MEMBER(self, folderitem->folder->name, "mailbox_name");
+    self->folderitem = folderitem;
+  }
+
+  return 0;
+
+ err:
+  return -1;
+}
+
+static PyObject* Folder_str(PyObject *self)
+{
+  PyObject *str;
+  str = PyString_FromString("Folder: ");
+  PyString_ConcatAndDel(&str, PyObject_GetAttrString(self, "name"));
+  return str;
+}
+
+static PyObject* Folder_get_identifier(clawsmail_FolderObject *self, PyObject *args)
+{
+  PyObject *obj;
+  gchar *id;
+  if(!self->folderitem)
+    return NULL;
+  id = folder_item_get_identifier(self->folderitem);
+  obj = Py_BuildValue("s", id);
+  g_free(id);
+  return obj;
+}
+
+static PyObject* Folder_get_messages(clawsmail_FolderObject *self, PyObject *args)
+{
+  GSList *msglist, *walk;
+  PyObject *retval;
+  Py_ssize_t pos;
+
+  if(!self->folderitem)
+    return NULL;
+
+  msglist = folder_item_get_msg_list(self->folderitem);
+  retval = PyTuple_New(g_slist_length(msglist));
+  if(!retval) {
+    procmsg_msg_list_free(msglist);
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+  
+  for(pos = 0, walk = msglist; walk; walk = walk->next, ++pos) {
+    PyObject *msg;
+    msg = clawsmail_messageinfo_new(walk->data);
+    PyTuple_SET_ITEM(retval, pos, msg);
+  }
+  procmsg_msg_list_free(msglist);
+  
+  return retval;
+}
+
+static PyMethodDef Folder_methods[] = {
+    {"get_identifier", (PyCFunction)Folder_get_identifier, METH_NOARGS,
+     "get_identifier() - get identifier\n"
+     "\n"
+     "Get identifier for folder as a string (e.g. #mh/foo/bar)."},
+    {"get_messages", (PyCFunction)Folder_get_messages, METH_NOARGS,
+     "get_messages() - get a tuple of messages in folder\n"
+     "\n"
+     "Get a tuple of MessageInfos for the folder."},
+    {NULL}
+};
+
+static PyMemberDef Folder_members[] = {
+  {"name", T_OBJECT_EX, offsetof(clawsmail_FolderObject, name), 0,
+   "name - name of folder"},
+  
+  {"path", T_OBJECT_EX, offsetof(clawsmail_FolderObject, path), 0,
+   "path - path of folder"},
+  
+  {"mailbox_name", T_OBJECT_EX, offsetof(clawsmail_FolderObject, mailbox_name), 0,
+   "mailbox_name - name of the corresponding mailbox"},
+
+  {NULL}
+};
+
+static PyTypeObject clawsmail_FolderType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                         /* ob_size*/
+    "clawsmail.Folder",        /* tp_name*/
+    sizeof(clawsmail_FolderObject), /* tp_basicsize*/
+    0,                         /* tp_itemsize*/
+    (destructor)Folder_dealloc, /* tp_dealloc*/
+    0,                         /* tp_print*/
+    0,                         /* tp_getattr*/
+    0,                         /* tp_setattr*/
+    0,                         /* tp_compare*/
+    0,                         /* tp_repr*/
+    0,                         /* tp_as_number*/
+    0,                         /* tp_as_sequence*/
+    0,                         /* tp_as_mapping*/
+    0,                         /* tp_hash */
+    0,                         /* tp_call*/
+    Folder_str,                /* tp_str*/
+    0,                         /* tp_getattro*/
+    0,                         /* tp_setattro*/
+    0,                         /* tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT,        /* tp_flags*/
+    "Folder objects.\n\n"      /* tp_doc */
+    "The __init__ function takes two optional arguments:\n"
+    "folder = Folder(identifier, [create_if_not_existing=False])\n"
+    "The identifier is an id string (e.g. '#mh/Mail/foo/bar'),"
+    "create_if_not_existing is a boolean expression.",
+    0,                         /* tp_traverse */
+    0,                         /* tp_clear */
+    0,                         /* tp_richcompare */
+    0,                         /* tp_weaklistoffset */
+    0,                         /* tp_iter */
+    0,                         /* tp_iternext */
+    Folder_methods,            /* tp_methods */
+    Folder_members,            /* tp_members */
+    0,                         /* tp_getset */
+    0,                         /* tp_base */
+    0,                         /* tp_dict */
+    0,                         /* tp_descr_get */
+    0,                         /* tp_descr_set */
+    0,                         /* tp_dictoffset */
+    (initproc)Folder_init,     /* tp_init */
+    0,                         /* tp_alloc */
+    0,                         /* tp_new */
+};
+
+PyMODINIT_FUNC initfolder(PyObject *module)
+{
+    clawsmail_FolderType.tp_new = PyType_GenericNew;
+    if(PyType_Ready(&clawsmail_FolderType) < 0)
+        return;
+
+    Py_INCREF(&clawsmail_FolderType);
+    PyModule_AddObject(module, "Folder", (PyObject*)&clawsmail_FolderType);
+}
+
+PyObject* clawsmail_folder_new(FolderItem *folderitem)
+{
+  clawsmail_FolderObject *ff;
+  PyObject *arglist;
+  gchar *id;
+
+  if(!folderitem)
+    return NULL;
+
+  id = folder_item_get_identifier(folderitem);
+  arglist = Py_BuildValue("(s)", id);
+  g_free(id);
+  ff = (clawsmail_FolderObject*) PyObject_CallObject((PyObject*) &clawsmail_FolderType, arglist);
+  Py_DECREF(arglist);
+  return (PyObject*)ff;
+}
+
+FolderItem* clawsmail_folder_get_item(PyObject *self)
+{
+  return ((clawsmail_FolderObject*)self)->folderitem;
+}
+
+PyTypeObject* clawsmail_folder_get_type_object()
+{
+  return &clawsmail_FolderType;
+}
+
+gboolean clawsmail_folder_check(PyObject *self)
+{
+  return (PyObject_TypeCheck(self, &clawsmail_FolderType) != 0);
+}
diff --git a/src/plugins/python/foldertype.h b/src/plugins/python/foldertype.h
new file mode 100644 (file)
index 0000000..7a913b1
--- /dev/null
@@ -0,0 +1,37 @@
+/* Python plugin for Claws-Mail
+ * Copyright (C) 2009 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FOLDERTYPE_H
+#define FOLDERTYPE_H
+
+#include <Python.h>
+
+#include "folder.h"
+
+#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
+#define PyMODINIT_FUNC void
+#endif
+
+PyMODINIT_FUNC initfolder(PyObject *module);
+
+PyObject* clawsmail_folder_new(FolderItem *folderitem);
+FolderItem* clawsmail_folder_get_item(PyObject *self);
+PyTypeObject* clawsmail_folder_get_type_object();
+
+gboolean clawsmail_folder_check(PyObject *self);
+
+#endif /* FOLDERTYPE_H */
diff --git a/src/plugins/python/messageinfotype.c b/src/plugins/python/messageinfotype.c
new file mode 100644 (file)
index 0000000..e796858
--- /dev/null
@@ -0,0 +1,396 @@
+/* Python plugin for Claws-Mail
+ * Copyright (C) 2009-2012 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "messageinfotype.h"
+
+#include "common/tags.h"
+#include "mainwindow.h"
+#include "summaryview.h"
+
+#include <structmember.h>
+
+
+typedef struct {
+    PyObject_HEAD
+    PyObject *from;
+    PyObject *to;
+    PyObject *subject;
+    PyObject *msgid;
+    PyObject *filepath;
+    MsgInfo *msginfo;
+} clawsmail_MessageInfoObject;
+
+
+static void MessageInfo_dealloc(clawsmail_MessageInfoObject* self)
+{
+  Py_XDECREF(self->from);
+  Py_XDECREF(self->to);
+  Py_XDECREF(self->subject);
+  Py_XDECREF(self->msgid);
+  self->ob_type->tp_free((PyObject*)self);
+}
+
+static int MessageInfo_init(clawsmail_MessageInfoObject *self, PyObject *args, PyObject *kwds)
+{
+  Py_INCREF(Py_None);
+  self->from = Py_None;
+
+  Py_INCREF(Py_None);
+  self->to = Py_None;
+
+  Py_INCREF(Py_None);
+  self->subject = Py_None;
+
+  Py_INCREF(Py_None);
+  self->msgid = Py_None;
+
+  return 0;
+}
+
+static PyObject* MessageInfo_str(PyObject *self)
+{
+  PyObject *str;
+  str = PyString_FromString("MessageInfo: ");
+  PyString_ConcatAndDel(&str, PyObject_GetAttrString(self, "From"));
+  PyString_ConcatAndDel(&str, PyString_FromString(" / "));
+  PyString_ConcatAndDel(&str, PyObject_GetAttrString(self, "Subject"));
+  return str;
+}
+
+static PyObject *py_boolean_return_value(gboolean val)
+{
+  if(val) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+static PyObject *is_new(PyObject *self, PyObject *args)
+{
+  return py_boolean_return_value(MSG_IS_NEW(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
+}
+
+static PyObject *is_unread(PyObject *self, PyObject *args)
+{
+  return py_boolean_return_value(MSG_IS_UNREAD(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
+}
+
+static PyObject *is_marked(PyObject *self, PyObject *args)
+{
+  return py_boolean_return_value(MSG_IS_MARKED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
+}
+
+static PyObject *is_replied(PyObject *self, PyObject *args)
+{
+  return py_boolean_return_value(MSG_IS_REPLIED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
+}
+
+static PyObject *is_locked(PyObject *self, PyObject *args)
+{
+  return py_boolean_return_value(MSG_IS_LOCKED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
+}
+
+static PyObject *is_forwarded(PyObject *self, PyObject *args)
+{
+  return py_boolean_return_value(MSG_IS_FORWARDED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
+}
+
+static PyObject* get_tags(PyObject *self, PyObject *args)
+{
+  GSList *tags_list;
+  Py_ssize_t num_tags;
+  PyObject *tags_tuple;
+
+  tags_list = ((clawsmail_MessageInfoObject*)self)->msginfo->tags;
+  num_tags = g_slist_length(tags_list);
+
+  tags_tuple = PyTuple_New(num_tags);
+  if(tags_tuple != NULL) {
+    Py_ssize_t iTag;
+    PyObject *tag_object;
+    GSList *walk;
+
+    iTag = 0;
+    for(walk = tags_list; walk; walk = walk->next) {
+      tag_object = Py_BuildValue("s", tags_get_tag(GPOINTER_TO_INT(walk->data)));
+      if(tag_object == NULL) {
+        Py_DECREF(tags_tuple);
+        return NULL;
+      }
+      PyTuple_SET_ITEM(tags_tuple, iTag++, tag_object);
+    }
+  }
+
+  return tags_tuple;
+}
+
+
+static PyObject* add_or_remove_tag(PyObject *self, PyObject *args, gboolean add)
+{
+  int retval;
+  const char *tag_str;
+  gint tag_id;
+  MsgInfo *msginfo;
+  MainWindow *mainwin;
+
+  retval = PyArg_ParseTuple(args, "s", &tag_str);
+  if(!retval)
+    return NULL;
+
+  tag_id = tags_get_id_for_str(tag_str);
+  if(tag_id == -1) {
+    PyErr_SetString(PyExc_ValueError, "Tag does not exist");
+    return NULL;
+  }
+
+  msginfo = ((clawsmail_MessageInfoObject*)self)->msginfo;
+
+  if(!add) {
+    /* raise KeyError if tag is not set */
+    if(!g_slist_find(msginfo->tags, GINT_TO_POINTER(tag_id))) {
+      PyErr_SetString(PyExc_KeyError, "Tag is not set on this message");
+      return NULL;
+    }
+  }
+
+  procmsg_msginfo_update_tags(msginfo, add, tag_id);
+
+  /* update display */
+  mainwin = mainwindow_get_mainwindow();
+  if(mainwin)
+    summary_redisplay_msg(mainwin->summaryview);
+
+  Py_RETURN_NONE;
+}
+
+
+
+static PyObject* add_tag(PyObject *self, PyObject *args)
+{
+  return add_or_remove_tag(self, args, TRUE);
+}
+
+
+static PyObject* remove_tag(PyObject *self, PyObject *args)
+{
+  return add_or_remove_tag(self, args, FALSE);
+}
+
+
+static PyMethodDef MessageInfo_methods[] = {
+  {"is_new",  is_new, METH_NOARGS,
+   "is_new() - checks if the message is new\n"
+   "\n"
+   "Returns True if the new flag of the message is set."},
+
+  {"is_unread",  is_unread, METH_NOARGS, 
+   "is_unread() - checks if the message is unread\n"
+   "\n"
+   "Returns True if the unread flag of the message is set."},
+
+  {"is_marked",  is_marked, METH_NOARGS,
+   "is_marked() - checks if the message is marked\n"
+   "\n"
+   "Returns True if the marked flag of the message is set."},
+
+  {"is_replied",  is_replied, METH_NOARGS,
+   "is_replied() - checks if the message has been replied to\n"
+   "\n"
+   "Returns True if the replied flag of the message is set."},
+
+  {"is_locked",  is_locked, METH_NOARGS,
+   "is_locked() - checks if the message has been locked\n"
+   "\n"
+   "Returns True if the locked flag of the message is set."},
+
+  {"is_forwarded",  is_forwarded, METH_NOARGS,
+   "is_forwarded() - checks if the message has been forwarded\n"
+   "\n"
+   "Returns True if the forwarded flag of the message is set."},
+
+  {"get_tags",  get_tags, METH_NOARGS,
+   "get_tags() - get message tags\n"
+   "\n"
+   "Returns a tuple of tags that apply to this message."},
+
+  {"add_tag",  add_tag, METH_VARARGS,
+   "add_tag(tag) - add a tag to this message\n"
+   "\n"
+   "Add a tag to this message. If the tag is already set, nothing is done.\n"
+   "If the tag does not exist, a ValueError exception is raised."},
+
+  {"remove_tag",  remove_tag, METH_VARARGS,
+   "remove_tag(tag) - remove a tag from this message\n"
+   "\n"
+   "Remove a tag from this message. If the tag is not set, a KeyError exception is raised.\n"
+   "If the tag does not exist, a ValueError exception is raised."},
+
+  {NULL}
+};
+
+static PyMemberDef MessageInfo_members[] = {
+    {"From", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, from), 0,
+     "From - the From header of the message"},
+
+    {"To", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, to), 0,
+     "To - the To header of the message"},
+
+    {"Subject", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, subject), 0,
+     "Subject - the subject header of the message"},
+
+    {"MessageID", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, msgid), 0,
+     "MessageID - the Message-ID header of the message"},
+
+    {"FilePath", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, filepath), 0,
+     "FilePath - path and filename of the message"},
+
+    {NULL}
+};
+
+static PyTypeObject clawsmail_MessageInfoType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                         /* ob_size*/
+    "clawsmail.MessageInfo",   /* tp_name*/
+    sizeof(clawsmail_MessageInfoObject), /* tp_basicsize*/
+    0,                         /* tp_itemsize*/
+    (destructor)MessageInfo_dealloc, /* tp_dealloc*/
+    0,                         /* tp_print*/
+    0,                         /* tp_getattr*/
+    0,                         /* tp_setattr*/
+    0,                         /* tp_compare*/
+    0,                         /* tp_repr*/
+    0,                         /* tp_as_number*/
+    0,                         /* tp_as_sequence*/
+    0,                         /* tp_as_mapping*/
+    0,                         /* tp_hash */
+    0,                         /* tp_call*/
+    MessageInfo_str,           /* tp_str*/
+    0,                         /* tp_getattro*/
+    0,                         /* tp_setattro*/
+    0,                         /* tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT,        /* tp_flags*/
+    "A MessageInfo represents" /* tp_doc */
+    "a single message.",
+    0,                         /* tp_traverse */
+    0,                         /* tp_clear */
+    0,                         /* tp_richcompare */
+    0,                         /* tp_weaklistoffset */
+    0,                         /* tp_iter */
+    0,                         /* tp_iternext */
+    MessageInfo_methods,       /* tp_methods */
+    MessageInfo_members,       /* tp_members */
+    0,                         /* tp_getset */
+    0,                         /* tp_base */
+    0,                         /* tp_dict */
+    0,                         /* tp_descr_get */
+    0,                         /* tp_descr_set */
+    0,                         /* tp_dictoffset */
+    (initproc)MessageInfo_init,/* tp_init */
+    0,                         /* tp_alloc */
+    0,                         /* tp_new */
+};
+
+PyMODINIT_FUNC initmessageinfo(PyObject *module)
+{
+    clawsmail_MessageInfoType.tp_new = PyType_GenericNew;
+    if(PyType_Ready(&clawsmail_MessageInfoType) < 0)
+        return;
+
+    Py_INCREF(&clawsmail_MessageInfoType);
+    PyModule_AddObject(module, "MessageInfo", (PyObject*)&clawsmail_MessageInfoType);
+}
+
+#define MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(fis, pms)     \
+  do {                                                            \
+    if(fis) {                                                     \
+      PyObject *str;                                              \
+      str = PyString_FromString(fis);                             \
+      if(str) {                                                   \
+        int retval;                                               \
+        retval = PyObject_SetAttrString((PyObject*)ff, pms, str); \
+        Py_DECREF(str);                                           \
+        if(retval == -1)                                          \
+          goto err;                                               \
+      }                                                           \
+    }                                                             \
+  } while(0)
+
+static gboolean update_members(clawsmail_MessageInfoObject *ff, MsgInfo *msginfo)
+{
+  gchar *filepath;
+
+  MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->from, "From");
+  MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->to, "To");
+  MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->subject, "Subject");
+  MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->msgid, "MessageID");
+
+  filepath = procmsg_get_message_file_path(msginfo);
+  if(filepath) {
+    MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(filepath, "FilePath");
+    g_free(filepath);
+  }
+  else {
+    MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER("", "FilePath");
+  }
+
+  return TRUE;
+err:
+  return FALSE;
+}
+
+PyObject* clawsmail_messageinfo_new(MsgInfo *msginfo)
+{
+  clawsmail_MessageInfoObject *ff;
+
+  if(!msginfo)
+    return NULL;
+
+  ff = (clawsmail_MessageInfoObject*) PyObject_CallObject((PyObject*) &clawsmail_MessageInfoType, NULL);
+  if(!ff)
+    return NULL;
+
+  ff->msginfo = msginfo;
+
+  if(update_members(ff, msginfo))
+    return (PyObject*)ff;
+  else {
+    Py_XDECREF(ff);
+    return NULL;
+  }
+}
+
+PyTypeObject* clawsmail_messageinfo_get_type_object()
+{
+  return &clawsmail_MessageInfoType;
+}
+
+MsgInfo* clawsmail_messageinfo_get_msginfo(PyObject *self)
+{
+  return ((clawsmail_MessageInfoObject*)self)->msginfo;
+}
diff --git a/src/plugins/python/messageinfotype.h b/src/plugins/python/messageinfotype.h
new file mode 100644 (file)
index 0000000..1beb37a
--- /dev/null
@@ -0,0 +1,35 @@
+/* Python plugin for Claws-Mail
+ * Copyright (C) 2009 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MESSAGEINFOTYPE_H
+#define MESSAGEINFOTYPE_H
+
+#include <Python.h>
+
+#include "procmsg.h"
+
+#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
+#define PyMODINIT_FUNC void
+#endif
+
+PyMODINIT_FUNC initmessageinfo(PyObject *module);
+
+PyObject* clawsmail_messageinfo_new(MsgInfo *msginfo);
+MsgInfo* clawsmail_messageinfo_get_msginfo(PyObject *self);
+PyTypeObject* clawsmail_messageinfo_get_type_object();
+
+#endif /* MESSAGEINFOTYPE_H */
diff --git a/src/plugins/python/nodetype.c b/src/plugins/python/nodetype.c
new file mode 100644 (file)
index 0000000..8bf8b65
--- /dev/null
@@ -0,0 +1,98 @@
+/* Python plugin for Claws-Mail
+ * Copyright (C) 2009 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "nodetype.h"
+
+#include <structmember.h>
+
+PyMODINIT_FUNC initnode(PyObject *module)
+{
+  PyObject *dict;
+  PyObject *res;
+  const char *cmd =
+      "class Node(object):\n"
+      "    \"\"\"A general purpose tree container type\"\"\"\n"
+      "\n"
+      "    def __init__(self):\n"
+      "        self.data = None\n"
+      "        self.children = []\n"
+      "\n"
+      "    def __str__(self):\n"
+      "        return '\\n'.join(self.get_str_list(0))\n"
+      "\n"
+      "    def get_str_list(self, level):\n"
+      "        \"\"\"get_str_list(level) - get a list of string-representations of the tree data\n"
+      "        \n"
+      "        The nesting of the tree elements is represented by various levels of indentation.\"\"\"\n"
+      "        str = []\n"
+      "        indent = '  '*level\n"
+      "        if self.data:\n"
+      "            str.append(indent + self.data.__str__())\n"
+      "        else:\n"
+      "            str.append(indent + 'None')\n"
+      "        for child in self.children:\n"
+      "            str.extend(child.get_str_list(level+1))\n"
+      "        return str\n"
+      "\n"
+      "    def traverse(self, callback, arg=None):\n"
+      "        \"\"\"traverse(callback [, arg=None]) - traverse the tree\n"
+      "        \n"
+      "        Traverse the tree, calling the callback function for each node element,\n"
+      "        with optional arg as user-data. The expected callback function signature is\n"
+      "        callback(node_data [, arg]).\"\"\"\n"
+      "        if self.data:\n"
+      "            if arg is not None:\n"
+      "                callback(self.data, arg)\n"
+      "            else:\n"
+      "                callback(self.data)\n"
+      "        for child in self.children:\n"
+      "            child.traverse(callback, arg)\n"
+      "\n"
+      "    def flat_list(self):\n"
+      "        \"\"\"flat_list() - get a flat list of the tree\n"
+      "        \n"
+      "        Returns a flat list of the tree, disregarding the nesting structure.\"\"\"\n"
+      "        flat_list = []\n"
+      "        self.traverse(lambda data,list: list.append(data), flat_list)\n"
+      "        return flat_list\n"
+      "\n";
+
+  dict = PyModule_GetDict(module);
+
+  if(PyDict_GetItemString(dict, "__builtins__") == NULL)
+    PyDict_SetItemString(dict, "__builtins__", PyEval_GetBuiltins());
+
+  res = PyRun_String(cmd, Py_file_input, dict, dict);
+  Py_XDECREF(res);
+}
+
+PyObject* clawsmail_node_new(PyObject *module)
+{
+  PyObject *class, *dict;
+
+  dict = PyModule_GetDict(module);
+  class = PyDict_GetItemString(dict, "Node");
+  return PyObject_CallObject(class, NULL);
+}
diff --git a/src/plugins/python/nodetype.h b/src/plugins/python/nodetype.h
new file mode 100644 (file)
index 0000000..cb437e6
--- /dev/null
@@ -0,0 +1,31 @@
+/* Python plugin for Claws-Mail
+ * Copyright (C) 2009 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NODETYPE_H
+#define NODETYPE_H
+
+#include <Python.h>
+
+#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
+#define PyMODINIT_FUNC void
+#endif
+
+PyMODINIT_FUNC initnode(PyObject *module);
+
+PyObject* clawsmail_node_new(PyObject *module);
+
+#endif /* NODETYPE_H */
diff --git a/src/plugins/python/placeholder.txt b/src/plugins/python/placeholder.txt
deleted file mode 100644 (file)
index 3b94f91..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Placeholder
diff --git a/src/plugins/python/python-hooks.c b/src/plugins/python/python-hooks.c
new file mode 100644 (file)
index 0000000..24822b6
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#ifdef ENABLE_PYTHON
+#include <Python.h>
+#include <pygobject.h>
+#include <pygtk/pygtk.h>
+#endif // ENABLE_PYTHON
+
+#include <dlfcn.h>
+
+#include <signal.h>
+
+#include "python-hooks.h"
+
+
+static gboolean python_enabled = FALSE;
+
+#ifdef ENABLE_PYTHON
+static GString *captured_stdout = NULL;
+static GString *captured_stderr = NULL;
+
+
+static PyObject *
+capture_stdout(PyObject *self, PyObject *args)
+{
+    char *str = NULL;
+
+    if (!PyArg_ParseTuple(args, "s", &str))
+        return NULL;
+
+    g_string_append(captured_stdout, str);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+capture_stderr(PyObject *self, PyObject *args)
+{
+    char *str = NULL;
+
+    if (!PyArg_ParseTuple(args, "s", &str))
+        return NULL;
+
+    g_string_append(captured_stderr, str);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+capture_stdin(PyObject *self, PyObject *args)
+{
+    /* Return an empty string.
+     * This is what read() returns when hitting EOF. */
+    return PyString_FromString("");
+}
+
+static PyObject *
+wrap_gobj(PyObject *self, PyObject *args)
+{
+    void *addr;
+    GObject *obj;
+
+    if (!PyArg_ParseTuple(args, "l", &addr))
+        return NULL;
+
+    if (!G_IS_OBJECT(addr))
+        return NULL; // XXX
+
+    obj = G_OBJECT(addr);
+
+    if (!obj)
+        return NULL; // XXX
+
+    return pygobject_new(obj);
+}
+
+static PyMethodDef parasite_python_methods[] = {
+    {"capture_stdout", capture_stdout, METH_VARARGS, "Captures stdout"},
+    {"capture_stderr", capture_stderr, METH_VARARGS, "Captures stderr"},
+    {"capture_stdin", capture_stdin, METH_VARARGS, "Captures stdin"},
+    {"gobj", wrap_gobj, METH_VARARGS, "Wraps a C GObject"},
+    {NULL, NULL, 0, NULL}
+};
+
+
+static gboolean
+is_blacklisted(void)
+{
+    const char *prgname = g_get_prgname();
+
+    return (!strcmp(prgname, "gimp"));
+}
+#endif // ENABLE_PYTHON
+
+void
+parasite_python_init(void)
+{
+#ifdef ENABLE_PYTHON
+    int res;
+    struct sigaction old_sigint;
+    PyObject *pygtk;
+
+    if (is_blacklisted())
+        return;
+
+    /* This prevents errors such as "undefined symbol: PyExc_ImportError" */
+    if (!dlopen(PYTHON_SHARED_LIB, RTLD_NOW | RTLD_GLOBAL))
+    {
+        g_error("%s\n", dlerror());
+        return;
+    }
+
+    captured_stdout = g_string_new("");
+    captured_stderr = g_string_new("");
+
+    /* Back up and later restore SIGINT so Python doesn't steal it from us. */
+    res = sigaction(SIGINT, NULL, &old_sigint);
+
+    if (!Py_IsInitialized())
+        Py_Initialize();
+
+    res = sigaction(SIGINT, &old_sigint, NULL);
+
+    Py_InitModule("parasite", parasite_python_methods);
+    PyRun_SimpleString(
+        "import parasite\n"
+        "import sys\n"
+        "\n"
+        "class StdoutCatcher:\n"
+        "    def write(self, str):\n"
+        "        parasite.capture_stdout(str)\n"
+        "    def flush(self):\n"
+        "        pass\n"
+        "\n"
+        "class StderrCatcher:\n"
+        "    def write(self, str):\n"
+        "        parasite.capture_stderr(str)\n"
+        "    def flush(self):\n"
+        "        pass\n"
+        "\n"
+        "class StdinCatcher:\n"
+        "    def readline(self, size=-1):\n"
+        "        return parasite.capture_stdin(size)\n"
+        "    def read(self, size=-1):\n"
+        "        return parasite.capture_stdin(size)\n"
+        "    def flush(self):\n"
+        "        pass\n"
+        "\n"
+    );
+
+    if (!pygobject_init(-1, -1, -1))
+        return;
+
+    pygtk = PyImport_ImportModule("gtk");
+
+    if (pygtk != NULL)
+    {
+        PyObject *module_dict = PyModule_GetDict(pygtk);
+        PyObject *cobject = PyDict_GetItemString(module_dict, "_PyGtk_API");
+
+        /*
+         * This seems to be NULL when we're running a PyGTK program.
+         * We really need to find out why.
+         */
+        if (cobject != NULL)
+        {
+            if (PyCObject_Check(cobject))
+                _PyGtk_API = (struct _PyGtk_FunctionStruct*)
+                PyCObject_AsVoidPtr(cobject);
+            else {
+                PyErr_SetString(PyExc_RuntimeError,
+                                "could not find _PyGtk_API object");
+                return;
+            }
+        }
+    } else {
+        PyErr_SetString(PyExc_ImportError, "could not import gtk");
+        return;
+    }
+
+    python_enabled = TRUE;
+#endif // ENABLE_PYTHON
+}
+
+void
+parasite_python_run(const char *command,
+                    ParasitePythonLogger stdout_logger,
+                    ParasitePythonLogger stderr_logger,
+                    gpointer user_data)
+{
+#ifdef ENABLE_PYTHON
+    PyGILState_STATE gstate;
+    PyObject *module;
+    PyObject *dict;
+    PyObject *obj;
+    const char *cp;
+
+    /* empty string as command is a noop */
+    if(!strcmp(command, ""))
+      return;
+
+    /* if first non-whitespace character is '#', command is also a noop */
+    cp = command;
+    while(cp && (*cp != '\0') && g_ascii_isspace(*cp))
+      cp++;
+    if(cp && *cp == '#')
+      return;
+
+    gstate = PyGILState_Ensure();
+
+    module = PyImport_AddModule("__main__");
+    dict = PyModule_GetDict(module);
+
+    PyRun_SimpleString("old_stdout = sys.stdout\n"
+                       "old_stderr = sys.stderr\n"
+                       "old_stdin  = sys.stdin\n"
+                       "sys.stdout = StdoutCatcher()\n"
+                       "sys.stderr = StderrCatcher()\n"
+                       "sys.stdin  = StdinCatcher()\n");
+
+    obj = PyRun_String(command, Py_single_input, dict, dict);
+    if(PyErr_Occurred())
+      PyErr_Print();
+    PyRun_SimpleString("sys.stdout = old_stdout\n"
+                       "sys.stderr = old_stderr\n"
+                       "sys.stdin = old_stdin\n");
+
+    if (stdout_logger != NULL)
+        stdout_logger(captured_stdout->str, user_data);
+
+    if (stderr_logger != NULL)
+        stderr_logger(captured_stderr->str, user_data);
+
+    // Print any returned object
+    if (obj != NULL && obj != Py_None) {
+       PyObject *repr = PyObject_Repr(obj);
+       if (repr != NULL) {
+           char *string = PyString_AsString(repr);
+
+           stdout_logger(string, user_data);
+           stdout_logger("\n", user_data);
+        }
+
+        Py_XDECREF(repr);
+    }
+    Py_XDECREF(obj);
+
+    PyGILState_Release(gstate);
+    g_string_erase(captured_stdout, 0, -1);
+    g_string_erase(captured_stderr, 0, -1);
+#endif // ENABLE_PYTHON
+}
+
+gboolean
+parasite_python_is_enabled(void)
+{
+    return python_enabled;
+}
+
+// vim: set et sw=4 ts=4:
diff --git a/src/plugins/python/python-hooks.h b/src/plugins/python/python-hooks.h
new file mode 100644 (file)
index 0000000..d6bf1bd
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef _GTKPARASITE_PYTHON_MODULE_H_
+#define _GTKPARASITE_PYTHON_MODULE_H_
+
+#include <glib.h>
+
+
+typedef void (*ParasitePythonLogger)(const char *text, gpointer user_data);
+
+void parasite_python_init(void);
+void parasite_python_run(const char *command,
+                         ParasitePythonLogger stdout_logger,
+                         ParasitePythonLogger stderr_logger,
+                         gpointer user_data);
+gboolean parasite_python_is_enabled(void);
+
+#endif // _GTKPARASITE_PYTHON_MODULE_H_
diff --git a/src/plugins/python/python-shell.c b/src/plugins/python/python-shell.c
new file mode 100644 (file)
index 0000000..6d2f06d
--- /dev/null
@@ -0,0 +1,438 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <gdk/gdkkeysyms.h>
+#include <string.h>
+
+#include "python-hooks.h"
+#include "python-shell.h"
+
+#define MAX_HISTORY_LENGTH 20
+
+#define PARASITE_PYTHON_SHELL_GET_PRIVATE(obj) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((obj), PARASITE_TYPE_PYTHON_SHELL, \
+                                 ParasitePythonShellPrivate))
+
+typedef struct
+{
+    GtkWidget *textview;
+
+    GtkTextMark *scroll_mark;
+    GtkTextMark *line_start_mark;
+
+    GQueue *history;
+    GList *cur_history_item;
+
+    GString *pending_command;
+    gboolean in_block;
+
+} ParasitePythonShellPrivate;
+
+enum
+{
+    LAST_SIGNAL
+};
+
+
+/* Widget functions */
+static void parasite_python_shell_finalize(GObject *obj);
+
+/* Python integration */
+static void parasite_python_shell_write_prompt(GtkWidget *python_shell);
+static char *parasite_python_shell_get_input(GtkWidget *python_shell);
+
+/* Callbacks */
+static gboolean parasite_python_shell_key_press_cb(GtkWidget *textview,
+                                                   GdkEventKey *event,
+                                                   GtkWidget *python_shell);
+
+
+static GtkVBoxClass *parent_class = NULL;
+//static guint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE(ParasitePythonShell, parasite_python_shell, GTK_TYPE_VBOX);
+
+
+static void
+parasite_python_shell_class_init(ParasitePythonShellClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    parent_class = g_type_class_peek_parent(klass);
+
+    object_class->finalize = parasite_python_shell_finalize;
+
+    g_type_class_add_private(klass, sizeof(ParasitePythonShellPrivate));
+}
+
+static void
+parasite_python_shell_init(ParasitePythonShell *python_shell)
+{
+    ParasitePythonShellPrivate *priv =
+        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
+    GtkWidget *swin;
+    GtkTextBuffer *buffer;
+    GtkTextIter iter;
+    PangoFontDescription *font_desc;
+
+    priv->history = g_queue_new();
+
+    gtk_box_set_spacing(GTK_BOX(python_shell), 6);
+
+    swin = gtk_scrolled_window_new(NULL, NULL);
+    gtk_widget_show(swin);
+    gtk_box_pack_start(GTK_BOX(python_shell), swin, TRUE, TRUE, 0);
+    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
+                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin),
+                                        GTK_SHADOW_IN);
+
+    priv->textview = gtk_text_view_new();
+    gtk_widget_show(priv->textview);
+    gtk_container_add(GTK_CONTAINER(swin), priv->textview);
+    gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(priv->textview), TRUE);
+    gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(priv->textview), 3);
+    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(priv->textview), 3);
+    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(priv->textview), 3);
+
+    g_signal_connect(G_OBJECT(priv->textview), "key_press_event",
+                     G_CALLBACK(parasite_python_shell_key_press_cb),
+                     python_shell);
+
+    /* Make the textview monospaced */
+    font_desc = pango_font_description_from_string("monospace");
+    pango_font_description_set_size(font_desc, 10 * PANGO_SCALE);
+    gtk_widget_modify_font(priv->textview, font_desc);
+    pango_font_description_free(font_desc);
+
+    /* Create the end-of-buffer mark */
+    buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
+    gtk_text_buffer_get_end_iter(buffer, &iter);
+    priv->scroll_mark = gtk_text_buffer_create_mark(buffer, "scroll_mark",
+                                                    &iter, FALSE);
+
+    /* Create the beginning-of-line mark */
+    priv->line_start_mark = gtk_text_buffer_create_mark(buffer,
+                                                        "line_start_mark",
+                                                        &iter, TRUE);
+
+    /* Register some tags */
+    gtk_text_buffer_create_tag(buffer, "stdout", NULL);
+    gtk_text_buffer_create_tag(buffer, "stderr",
+                               "foreground", "red",
+                               "paragraph-background", "#FFFFE0",
+                               NULL);
+    gtk_text_buffer_create_tag(buffer, "prompt",
+                               "foreground", "blue",
+                               NULL);
+
+    parasite_python_shell_write_prompt(GTK_WIDGET(python_shell));
+}
+
+static void
+parasite_python_shell_finalize(GObject *python_shell)
+{
+    ParasitePythonShellPrivate *priv =
+        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
+
+    g_queue_free(priv->history);
+}
+
+static void
+parasite_python_shell_log_stdout(const char *text, gpointer python_shell)
+{
+    parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
+                                      text, "stdout");
+}
+
+static void
+parasite_python_shell_log_stderr(const char *text, gpointer python_shell)
+{
+    parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
+                                      text, "stderr");
+}
+
+static void
+parasite_python_shell_write_prompt(GtkWidget *python_shell)
+{
+    ParasitePythonShellPrivate *priv =
+        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
+    GtkTextBuffer *buffer =
+        gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
+    GtkTextIter iter;
+    const char *prompt = (priv->pending_command == NULL ? ">>> " : "... ");
+
+    parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
+                                      prompt, "prompt");
+
+    gtk_text_buffer_get_end_iter(buffer, &iter);
+    gtk_text_buffer_move_mark(buffer, priv->line_start_mark, &iter);
+}
+
+static void
+parasite_python_shell_process_line(GtkWidget *python_shell)
+{
+    ParasitePythonShellPrivate *priv =
+        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
+
+    char *command = parasite_python_shell_get_input(python_shell);
+    char last_char;
+
+    parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
+                                      "\n", NULL);
+
+    if (*command != '\0')
+    {
+        /* Save this command in the history. */
+        g_queue_push_head(priv->history, command);
+        priv->cur_history_item = NULL;
+
+        if (g_queue_get_length(priv->history) > MAX_HISTORY_LENGTH)
+            g_free(g_queue_pop_tail(priv->history));
+    }
+
+    last_char = command[MAX(0, strlen(command) - 1)];
+
+    if (last_char == ':' || last_char == '\\' ||
+        (priv->in_block && g_ascii_isspace(command[0])))
+    {
+        printf("in block.. %c, %d, %d\n",
+               last_char, priv->in_block,
+               g_ascii_isspace(command[0]));
+        /* This is a multi-line expression */
+        if (priv->pending_command == NULL)
+            priv->pending_command = g_string_new(command);
+        else
+            g_string_append(priv->pending_command, command);
+
+        g_string_append_c(priv->pending_command, '\n');
+
+        if (last_char == ':')
+            priv->in_block = TRUE;
+    }
+    else
+    {
+        if (priv->pending_command != NULL)
+        {
+            g_string_append(priv->pending_command, command);
+            g_string_append_c(priv->pending_command, '\n');
+
+            /* We're not actually leaking this. It's in the history. */
+            command = g_string_free(priv->pending_command, FALSE);
+        }
+        parasite_python_run(command,
+                            parasite_python_shell_log_stdout,
+                            parasite_python_shell_log_stderr,
+                            python_shell);
+        if (priv->pending_command != NULL)
+        {
+            /* Now do the cleanup. */
+            g_free(command);
+            priv->pending_command = NULL;
+            priv->in_block = FALSE;
+        }
+    }
+    parasite_python_shell_write_prompt(python_shell);
+}
+
+static void
+parasite_python_shell_replace_input(GtkWidget *python_shell,
+                                    const char *text)
+{
+    ParasitePythonShellPrivate *priv =
+        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
+
+    GtkTextBuffer *buffer =
+        gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
+    GtkTextIter start_iter;
+    GtkTextIter end_iter;
+
+    gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
+                                     priv->line_start_mark);
+    gtk_text_buffer_get_end_iter(buffer, &end_iter);
+
+    gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
+    gtk_text_buffer_insert(buffer, &end_iter, text, -1);
+}
+
+static char *
+parasite_python_shell_get_input(GtkWidget *python_shell)
+{
+    ParasitePythonShellPrivate *priv =
+        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
+    GtkTextBuffer *buffer =
+        gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
+    GtkTextIter start_iter;
+    GtkTextIter end_iter;
+
+    gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
+                                     priv->line_start_mark);
+    gtk_text_buffer_get_end_iter(buffer, &end_iter);
+
+    return gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE);
+}
+
+static const char *
+parasite_python_shell_get_history_back(GtkWidget *python_shell)
+{
+    ParasitePythonShellPrivate *priv =
+        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
+
+    if (priv->cur_history_item == NULL)
+    {
+        priv->cur_history_item = g_queue_peek_head_link(priv->history);
+
+        if (priv->cur_history_item == NULL)
+            return "";
+    }
+    else if (priv->cur_history_item->next != NULL)
+        priv->cur_history_item = priv->cur_history_item->next;
+
+    return (const char *)priv->cur_history_item->data;
+}
+
+static const char *
+parasite_python_shell_get_history_forward(GtkWidget *python_shell)
+{
+    ParasitePythonShellPrivate *priv =
+        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
+
+    if (priv->cur_history_item == NULL || priv->cur_history_item->prev == NULL)
+    {
+        priv->cur_history_item = NULL;
+        return "";
+    }
+
+    priv->cur_history_item = priv->cur_history_item->prev;
+
+    return (const char *)priv->cur_history_item->data;
+}
+
+static gboolean
+parasite_python_shell_key_press_cb(GtkWidget *textview,
+                                   GdkEventKey *event,
+                                   GtkWidget *python_shell)
+{
+    if (event->keyval == GDK_Return)
+    {
+        parasite_python_shell_process_line(python_shell);
+        return TRUE;
+    }
+    else if (event->keyval == GDK_Up)
+    {
+        parasite_python_shell_replace_input(python_shell,
+            parasite_python_shell_get_history_back(python_shell));
+        return TRUE;
+    }
+    else if (event->keyval == GDK_Down)
+    {
+        parasite_python_shell_replace_input(python_shell,
+            parasite_python_shell_get_history_forward(python_shell));
+        return TRUE;
+    }
+    else if (event->string != NULL)
+    {
+        ParasitePythonShellPrivate *priv =
+            PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
+        GtkTextBuffer *buffer =
+            gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
+        GtkTextMark *insert_mark = gtk_text_buffer_get_insert(buffer);
+        GtkTextMark *selection_mark =
+            gtk_text_buffer_get_selection_bound(buffer);
+        GtkTextIter insert_iter;
+        GtkTextIter selection_iter;
+        GtkTextIter start_iter;
+        gint cmp_start_insert;
+        gint cmp_start_select;
+        gint cmp_insert_select;
+
+        gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
+                                         priv->line_start_mark);
+        gtk_text_buffer_get_iter_at_mark(buffer, &insert_iter, insert_mark);
+        gtk_text_buffer_get_iter_at_mark(buffer, &selection_iter,
+                                         selection_mark);
+
+        cmp_start_insert = gtk_text_iter_compare(&start_iter, &insert_iter);
+        cmp_start_select = gtk_text_iter_compare(&start_iter, &selection_iter);
+        cmp_insert_select = gtk_text_iter_compare(&insert_iter,
+                                                  &selection_iter);
+
+        if (cmp_start_insert == 0 && cmp_start_select == 0 &&
+            (event->keyval == GDK_BackSpace ||
+             event->keyval == GDK_Left))
+        {
+            return TRUE;
+        }
+        if (cmp_start_insert <= 0 && cmp_start_select <= 0)
+        {
+            return FALSE;
+        }
+        else if (cmp_start_insert > 0 && cmp_start_select > 0)
+        {
+            gtk_text_buffer_place_cursor(buffer, &start_iter);
+        }
+        else if (cmp_insert_select < 0)
+        {
+            gtk_text_buffer_move_mark(buffer, insert_mark, &start_iter);
+        }
+        else if (cmp_insert_select > 0)
+        {
+            gtk_text_buffer_move_mark(buffer, selection_mark, &start_iter);
+        }
+    }
+
+    return FALSE;
+}
+
+GtkWidget *
+parasite_python_shell_new(void)
+{
+    return g_object_new(PARASITE_TYPE_PYTHON_SHELL, NULL);
+}
+
+void
+parasite_python_shell_append_text(ParasitePythonShell *python_shell,
+                                  const char *str,
+                                  const char *tag)
+{
+    ParasitePythonShellPrivate *priv =
+        PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
+
+    GtkTextIter end;
+    GtkTextBuffer *buffer =
+        gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
+    GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
+
+    gtk_text_buffer_get_end_iter(buffer, &end);
+    gtk_text_buffer_move_mark(buffer, mark, &end);
+    gtk_text_buffer_insert_with_tags_by_name(buffer, &end, str, -1, tag, NULL);
+    gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(priv->textview), mark,
+                                 0, TRUE, 0, 1);
+}
+
+void
+parasite_python_shell_focus(ParasitePythonShell *python_shell)
+{
+   gtk_widget_grab_focus(PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell)->textview);
+}
+
+// vim: set et ts=4:
diff --git a/src/plugins/python/python-shell.h b/src/plugins/python/python-shell.h
new file mode 100644 (file)
index 0000000..ad74165
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2008-2009  Christian Hammond
+ * Copyright (c) 2008-2009  David Trowbridge
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef _PARASITE_PYTHON_SHELL_H_
+#define _PARASITE_PYTHON_SHELL_H_
+
+typedef struct _ParasitePythonShell      ParasitePythonShell;
+typedef struct _ParasitePythonShellClass ParasitePythonShellClass;
+
+#include <gtk/gtk.h>
+
+#define PARASITE_TYPE_PYTHON_SHELL (parasite_python_shell_get_type())
+#define PARASITE_PYTHON_SHELL(obj) \
+               (G_TYPE_CHECK_INSTANCE_CAST((obj), PARASITE_TYPE_PYTHON_SHELL, ParasitePythonShell))
+#define PARASITE_PYTHON_SHELL_CLASS(klass) \
+               (G_TYPE_CHECK_CLASS_CAST((klass), PARASITE_TYPE_PYTHON_SHELL, ParasitePythonShellClass))
+#define PARASITE_IS_PYTHON_SHELL(obj) \
+               (G_TYPE_CHECK_INSTANCE_TYPE((obj), PARASITE_TYPE_PYTHON_SHELL))
+#define PARASITE_IS_PYTHON_SHELL_CLASS(klass) \
+               (G_TYPE_CHECK_CLASS_TYPE((klass), PARASITE_TYPE_PYTHON_SHELL))
+#define PARASITE_PYTHON_SHELL_GET_CLASS(obj) \
+               (G_TYPE_INSTANCE_GET_CLASS ((obj), PARASITE_TYPE_PYTHON_SHELL, ParasitePythonShellClass))
+
+
+struct _ParasitePythonShell
+{
+       GtkVBox parent_object;
+
+       void (*gtk_reserved1)(void);
+       void (*gtk_reserved2)(void);
+       void (*gtk_reserved3)(void);
+       void (*gtk_reserved4)(void);
+};
+
+struct _ParasitePythonShellClass
+{
+       GtkVBoxClass parent_class;
+
+       void (*gtk_reserved1)(void);
+       void (*gtk_reserved2)(void);
+       void (*gtk_reserved3)(void);
+       void (*gtk_reserved4)(void);
+};
+
+G_BEGIN_DECLS
+
+GType parasite_python_shell_get_type(void);
+
+GtkWidget *parasite_python_shell_new(void);
+void parasite_python_shell_append_text(ParasitePythonShell *python_shell,
+                                       const char *str,
+                                       const char *tag);
+void parasite_python_shell_focus(ParasitePythonShell *python_shell);
+
+G_END_DECLS
+
+#endif // _PARASITE_PYTHON_SHELL_H_
diff --git a/src/plugins/python/python_plugin.c b/src/plugins/python/python_plugin.c
new file mode 100644 (file)
index 0000000..9c34538
--- /dev/null
@@ -0,0 +1,639 @@
+/* Python plugin for Claws-Mail
+ * Copyright (C) 2009 Holger Berndt
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <Python.h>
+
+#include "common/hooks.h"
+#include "common/plugin.h"
+#include "common/version.h"
+#include "common/utils.h"
+#include "gtk/menu.h"
+#include "main.h"
+#include "mainwindow.h"
+#include "prefs_toolbar.h"
+
+#include "python-shell.h"
+#include "python-hooks.h"
+#include "clawsmailmodule.h"
+
+#define PYTHON_SCRIPTS_BASE_DIR "python-scripts"
+#define PYTHON_SCRIPTS_MAIN_DIR "main"
+#define PYTHON_SCRIPTS_COMPOSE_DIR "compose"
+#define PYTHON_SCRIPTS_AUTO_DIR "auto"
+#define PYTHON_SCRIPTS_AUTO_STARTUP "startup"
+#define PYTHON_SCRIPTS_AUTO_SHUTDOWN "shutdown"
+#define PYTHON_SCRIPTS_AUTO_COMPOSE "compose_any"
+#define PYTHON_SCRIPTS_ACTION_PREFIX "Tools/PythonScripts/"
+
+static GSList *menu_id_list = NULL;
+static GSList *python_mainwin_scripts_id_list = NULL;
+static GSList *python_mainwin_scripts_names = NULL;
+static GSList *python_compose_scripts_names = NULL;
+
+static GtkWidget *python_console = NULL;
+
+static guint hook_compose_create;
+
+static gboolean python_console_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+  MainWindow *mainwin;
+  GtkToggleAction *action;
+
+  mainwin =  mainwindow_get_mainwindow();
+  action = GTK_TOGGLE_ACTION(gtk_action_group_get_action(mainwin->action_group, "Tools/ShowPythonConsole"));
+  gtk_toggle_action_set_active(action, FALSE);
+  return TRUE;
+}
+
+static void setup_python_console(void)
+{
+  GtkWidget *vbox;
+  GtkWidget *console;
+
+  python_console = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  gtk_widget_set_size_request(python_console, 600, 400);
+
+  vbox = gtk_vbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER(python_console), vbox);
+
+  console = parasite_python_shell_new();
+  gtk_box_pack_start(GTK_BOX(vbox), console, TRUE, TRUE, 0);
+
+  g_signal_connect(python_console, "delete-event", G_CALLBACK(python_console_delete_event), NULL);
+
+  gtk_widget_show_all(python_console);
+
+  parasite_python_shell_focus(PARASITE_PYTHON_SHELL(console));
+}
+
+static void show_hide_python_console(GtkToggleAction *action, gpointer callback_data)
+{
+  if(gtk_toggle_action_get_active(action)) {
+    if(!python_console)
+      setup_python_console();
+    gtk_widget_show(python_console);
+  }
+  else {
+    gtk_widget_hide(python_console);
+  }
+}
+
+static void remove_python_scripts_menus(void)
+{
+  GSList *walk;
+  MainWindow *mainwin;
+
+  mainwin =  mainwindow_get_mainwindow();
+
+  /* toolbar */
+  for(walk = python_mainwin_scripts_names; walk; walk = walk->next)
+    prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, "Python", walk->data);
+
+  /* ui */
+  for(walk = python_mainwin_scripts_id_list; walk; walk = walk->next)
+      gtk_ui_manager_remove_ui(mainwin->ui_manager, GPOINTER_TO_UINT(walk->data));
+  g_slist_free(python_mainwin_scripts_id_list);
+  python_mainwin_scripts_id_list = NULL;
+
+  /* actions */
+  for(walk = python_mainwin_scripts_names; walk; walk = walk->next) {
+    GtkAction *action;
+    gchar *entry;
+    entry = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
+    action = gtk_action_group_get_action(mainwin->action_group, entry);
+    g_free(entry);
+    if(action)
+      gtk_action_group_remove_action(mainwin->action_group, action);
+    g_free(walk->data);
+  }
+  g_slist_free(python_mainwin_scripts_names);
+  python_mainwin_scripts_names = NULL;
+
+  /* compose scripts */
+  for(walk = python_compose_scripts_names; walk; walk = walk->next) {
+    prefs_toolbar_unregister_plugin_item(TOOLBAR_COMPOSE, "Python", walk->data);
+    g_free(walk->data);
+  }
+  g_slist_free(python_compose_scripts_names);
+  python_compose_scripts_names = NULL;
+}
+
+static gchar* extract_filename(const gchar *str)
+{
+  gchar *filename;
+
+  filename = g_strrstr(str, "/");
+  if(!filename || *(filename+1) == '\0') {
+    debug_print("Error: Could not extract filename from %s\n", str);
+    return NULL;
+  }
+  filename++;
+  return filename;
+}
+
+static void run_script_file(const gchar *filename, Compose *compose)
+{
+  FILE *fp;
+  fp = fopen(filename, "r");
+  if(!fp) {
+    g_print("Error: Could not open file '%s'\n", filename);
+    return;
+  }
+  put_composewindow_into_module(compose);
+  PyRun_SimpleFile(fp, filename);
+  fclose(fp);
+}
+
+static void run_auto_script_file_if_it_exists(const gchar *autofilename, Compose *compose)
+{
+  gchar *auto_filepath;
+
+  /* execute auto/autofilename, if it exists */
+  auto_filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+      PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
+      PYTHON_SCRIPTS_AUTO_DIR, G_DIR_SEPARATOR_S, autofilename, NULL);
+  if(file_exist(auto_filepath, FALSE))
+    run_script_file(auto_filepath, compose);
+  g_free(auto_filepath);
+}
+
+static void python_mainwin_script_callback(GtkAction *action, gpointer data)
+{
+  char *filename;
+
+  filename = extract_filename(data);
+  if(!filename)
+    return;
+  filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_MAIN_DIR, G_DIR_SEPARATOR_S, filename, NULL);
+  run_script_file(filename, NULL);
+  g_free(filename);
+}
+
+typedef struct _ComposeActionData ComposeActionData;
+struct _ComposeActionData {
+  gchar *name;
+  Compose *compose;
+};
+
+static void python_compose_script_callback(GtkAction *action, gpointer data)
+{
+  char *filename;
+  ComposeActionData *dat = (ComposeActionData*)data;
+
+  filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_COMPOSE_DIR, G_DIR_SEPARATOR_S, dat->name, NULL);
+  run_script_file(filename, dat->compose);
+
+  g_free(filename);
+}
+
+static void mainwin_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
+{
+       gchar *script;
+       script = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, item_name, NULL);
+       python_mainwin_script_callback(NULL, script);
+       g_free(script);
+}
+
+static void compose_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
+{
+  gchar *filename;
+
+  filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+      PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
+      PYTHON_SCRIPTS_COMPOSE_DIR, G_DIR_SEPARATOR_S,
+      item_name, NULL);
+  run_script_file(filename, (Compose*)parent);
+  g_free(filename);
+}
+
+static void make_sure_script_directory_exists(const gchar *subdir)
+{
+  char *dir;
+  dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, subdir, NULL);
+  if(!g_file_test(dir, G_FILE_TEST_IS_DIR)) {
+    if(g_mkdir(dir, 0777) != 0)
+      debug_print("Python plugin: Could not create directory '%s'\n", dir);
+  }
+  g_free(dir);
+}
+
+static void make_sure_directories_exist(void)
+{
+  make_sure_script_directory_exists("");
+  make_sure_script_directory_exists(PYTHON_SCRIPTS_MAIN_DIR);
+  make_sure_script_directory_exists(PYTHON_SCRIPTS_COMPOSE_DIR);
+  make_sure_script_directory_exists(PYTHON_SCRIPTS_AUTO_DIR);
+}
+
+static void migrate_scripts_out_of_base_dir(void)
+{
+  char *base_dir;
+  GDir *dir;
+  const char *filename;
+  gchar *dest_dir;
+
+  base_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, NULL);
+  dir = g_dir_open(base_dir, 0, NULL);
+  g_free(base_dir);
+  if(!dir)
+    return;
+
+  dest_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+      PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
+      PYTHON_SCRIPTS_MAIN_DIR, NULL);
+  if(!g_file_test(dest_dir, G_FILE_TEST_IS_DIR)) {
+    if(g_mkdir(dest_dir, 0777) != 0) {
+      g_free(dest_dir);
+      g_dir_close(dir);
+      return;
+    }
+  }
+
+  while((filename = g_dir_read_name(dir)) != NULL) {
+    gchar *filepath;
+    filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, filename, NULL);
+    if(g_file_test(filepath, G_FILE_TEST_IS_REGULAR)) {
+      gchar *dest_file;
+      dest_file = g_strconcat(dest_dir, G_DIR_SEPARATOR_S, filename, NULL);
+      if(move_file(filepath, dest_file, FALSE) == 0)
+        g_print("Python plugin: Moved file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
+      else
+        g_print("Python plugin: Warning: Could not move file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
+      g_free(dest_file);
+    }
+    g_free(filepath);
+  }
+  g_dir_close(dir);
+  g_free(dest_dir);
+}
+
+
+static void create_mainwindow_menus_and_items(GSList *filenames, gint num_entries)
+{
+  MainWindow *mainwin;
+  gint ii;
+  GSList *walk;
+  GtkActionEntry *entries;
+
+  /* create menu items */
+  entries = g_new0(GtkActionEntry, num_entries);
+  ii = 0;
+  mainwin =  mainwindow_get_mainwindow();
+  for(walk = filenames; walk; walk = walk->next) {
+    entries[ii].name = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
+    entries[ii].label = walk->data;
+    entries[ii].callback = G_CALLBACK(python_mainwin_script_callback);
+    gtk_action_group_add_actions(mainwin->action_group, &(entries[ii]), 1, (gpointer)entries[ii].name);
+    ii++;
+  }
+  for(ii = 0; ii < num_entries; ii++) {
+    guint id;
+
+    python_mainwin_scripts_names = g_slist_prepend(python_mainwin_scripts_names, g_strdup(entries[ii].label));
+    MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/" PYTHON_SCRIPTS_ACTION_PREFIX, entries[ii].label,
+        entries[ii].name, GTK_UI_MANAGER_MENUITEM, id)
+    python_mainwin_scripts_id_list = g_slist_prepend(python_mainwin_scripts_id_list, GUINT_TO_POINTER(id));
+
+    prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, "Python", entries[ii].label, mainwin_toolbar_callback, NULL);
+  }
+
+  g_free(entries);
+}
+
+
+/* this function doesn't really create menu items, but prepares a list that can be used
+ * in the compose create hook. It does however register the scripts for the toolbar editor */
+static void create_compose_menus_and_items(GSList *filenames)
+{
+  GSList *walk;
+  for(walk = filenames; walk; walk = walk->next) {
+    python_compose_scripts_names = g_slist_prepend(python_compose_scripts_names, g_strdup((gchar*)walk->data));
+    prefs_toolbar_register_plugin_item(TOOLBAR_COMPOSE, "Python", (gchar*)walk->data, compose_toolbar_callback, NULL);
+  }
+}
+
+static GtkActionEntry compose_tools_python_actions[] = {
+    {"Tools/PythonScripts", NULL, N_("Python scripts") },
+};
+
+static void ComposeActionData_destroy_cb(gpointer data)
+{
+  ComposeActionData *dat = (ComposeActionData*)data;
+  g_free(dat->name);
+  g_free(dat);
+}
+
+static gboolean my_compose_create_hook(gpointer cw, gpointer data)
+{
+  gint ii;
+  GSList *walk;
+  GtkActionEntry *entries;
+  GtkActionGroup *action_group;
+  Compose *compose = (Compose*)cw;
+  guint num_entries = g_slist_length(python_compose_scripts_names);
+
+  action_group = gtk_action_group_new("PythonPlugin");
+  gtk_action_group_add_actions(action_group, compose_tools_python_actions, 1, NULL);
+  entries = g_new0(GtkActionEntry, num_entries);
+  ii = 0;
+  for(walk = python_compose_scripts_names; walk; walk = walk->next) {
+    ComposeActionData *dat;
+
+    entries[ii].name = walk->data;
+    entries[ii].label = walk->data;
+    entries[ii].callback = G_CALLBACK(python_compose_script_callback);
+
+    dat = g_new0(ComposeActionData, 1);
+    dat->name = g_strdup(walk->data);
+    dat->compose = compose;
+
+    gtk_action_group_add_actions_full(action_group, &(entries[ii]), 1, dat, ComposeActionData_destroy_cb);
+    ii++;
+  }
+  gtk_ui_manager_insert_action_group(compose->ui_manager, action_group, 0);
+
+  MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "PythonScripts",
+      "Tools/PythonScripts", GTK_UI_MANAGER_MENU)
+
+  for(ii = 0; ii < num_entries; ii++) {
+    MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/" PYTHON_SCRIPTS_ACTION_PREFIX, entries[ii].label,
+        entries[ii].name, GTK_UI_MANAGER_MENUITEM)
+  }
+
+  g_free(entries);
+
+  run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_COMPOSE, compose);
+
+  return FALSE;
+}
+
+
+static void refresh_scripts_in_dir(const gchar *subdir, ToolbarType toolbar_type)
+{
+  char *scripts_dir;
+  GDir *dir;
+  GError *error = NULL;
+  const char *filename;
+  GSList *filenames = NULL;
+  GSList *walk;
+  gint num_entries;
+
+  scripts_dir = g_strconcat(get_rc_dir(),
+      G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR,
+      G_DIR_SEPARATOR_S, subdir,
+      NULL);
+  debug_print("Refreshing: %s\n", scripts_dir);
+
+  dir = g_dir_open(scripts_dir, 0, &error);
+  g_free(scripts_dir);
+
+  if(!dir) {
+    g_print("Could not open directory '%s': %s\n", subdir, error->message);
+    g_error_free(error);
+    return;
+  }
+
+  /* get filenames */
+  num_entries = 0;
+  while((filename = g_dir_read_name(dir)) != NULL) {
+    char *fn;
+
+    fn = g_strdup(filename);
+    filenames = g_slist_prepend(filenames, fn);
+    num_entries++;
+  }
+  g_dir_close(dir);
+
+  if(toolbar_type == TOOLBAR_MAIN)
+    create_mainwindow_menus_and_items(filenames, num_entries);
+  else if(toolbar_type == TOOLBAR_COMPOSE)
+    create_compose_menus_and_items(filenames);
+
+  /* cleanup */
+  for(walk = filenames; walk; walk = walk->next)
+    g_free(walk->data);
+  g_slist_free(filenames);
+}
+
+static void browse_python_scripts_dir(GtkAction *action, gpointer data)
+{
+  gchar *uri;
+  GdkAppLaunchContext *launch_context;
+  GError *error = NULL;
+  MainWindow *mainwin;
+
+  mainwin =  mainwindow_get_mainwindow();
+  if(!mainwin) {
+      debug_print("Browse Python scripts: Problems getting the mainwindow\n");
+      return;
+  }
+  launch_context = gdk_app_launch_context_new();
+  gdk_app_launch_context_set_screen(launch_context, gtk_widget_get_screen(mainwin->window));
+  uri = g_strconcat("file://", get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, NULL);
+  g_app_info_launch_default_for_uri(uri, launch_context, &error);
+
+  if(error) {
+      debug_print("Could not open scripts dir browser: '%s'\n", error->message);
+      g_error_free(error);
+  }
+
+  g_object_unref(launch_context);
+  g_free(uri);
+}
+
+static void refresh_python_scripts_menus(GtkAction *action, gpointer data)
+{
+  remove_python_scripts_menus();
+
+  migrate_scripts_out_of_base_dir();
+
+  refresh_scripts_in_dir(PYTHON_SCRIPTS_MAIN_DIR, TOOLBAR_MAIN);
+  refresh_scripts_in_dir(PYTHON_SCRIPTS_COMPOSE_DIR, TOOLBAR_COMPOSE);
+}
+
+static GtkToggleActionEntry mainwindow_tools_python_toggle[] = {
+    {"Tools/ShowPythonConsole", NULL, N_("Show Python console..."),
+        NULL, NULL, G_CALLBACK(show_hide_python_console), FALSE},
+};
+
+static GtkActionEntry mainwindow_tools_python_actions[] = {
+    {"Tools/PythonScripts", NULL, N_("Python scripts") },
+    {"Tools/PythonScripts/Refresh", NULL, N_("Refresh"),
+        NULL, NULL, G_CALLBACK(refresh_python_scripts_menus) },
+    {"Tools/PythonScripts/Browse", NULL, N_("Browse"),
+        NULL, NULL, G_CALLBACK(browse_python_scripts_dir) },
+    {"Tools/PythonScripts/---", NULL, "---" },
+};
+
+void python_menu_init(void)
+{
+  MainWindow *mainwin;
+  guint id;
+
+  mainwin =  mainwindow_get_mainwindow();
+
+  gtk_action_group_add_toggle_actions(mainwin->action_group, mainwindow_tools_python_toggle, 1, mainwin);
+  gtk_action_group_add_actions(mainwin->action_group, mainwindow_tools_python_actions, 3, mainwin);
+
+  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "ShowPythonConsole",
+      "Tools/ShowPythonConsole", GTK_UI_MANAGER_MENUITEM, id)
+  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
+
+  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "PythonScripts",
+      "Tools/PythonScripts", GTK_UI_MANAGER_MENU, id)
+  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
+
+  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Refresh",
+      "Tools/PythonScripts/Refresh", GTK_UI_MANAGER_MENUITEM, id)
+  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
+
+  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Browse",
+      "Tools/PythonScripts/Browse", GTK_UI_MANAGER_MENUITEM, id)
+  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
+
+  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Separator1",
+      "Tools/PythonScripts/---", GTK_UI_MANAGER_SEPARATOR, id)
+  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
+
+  refresh_python_scripts_menus(NULL, NULL);
+}
+
+void python_menu_done(void)
+{
+  MainWindow *mainwin;
+
+  mainwin = mainwindow_get_mainwindow();
+
+  if(mainwin && !claws_is_exiting()) {
+    GSList *walk;
+
+    remove_python_scripts_menus();
+
+    for(walk = menu_id_list; walk; walk = walk->next)
+      gtk_ui_manager_remove_ui(mainwin->ui_manager, GPOINTER_TO_UINT(walk->data));
+    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/ShowPythonConsole", 0);
+    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts", 0);
+    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/Refresh", 0);
+    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/Browse", 0);
+    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/---", 0);
+  }
+}
+
+gint plugin_init(gchar **error)
+{
+  /* Version check */
+  if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,6,9),
+                          VERSION_NUMERIC, _("Python"), error))
+    return -1;
+
+  /* load hooks */
+  hook_compose_create = hooks_register_hook(COMPOSE_CREATED_HOOKLIST, my_compose_create_hook, NULL);
+  if(hook_compose_create == (guint)-1) {
+    *error = g_strdup(_("Failed to register \"compose create hook\" in the Python plugin"));
+    return -1;
+  }
+
+  /* script directories */
+  make_sure_directories_exist();
+
+  /* initialize python interpreter */
+  Py_Initialize();
+
+  /* initialize python interactive shell */
+  parasite_python_init();
+
+  /* initialize Claws Mail Python module */
+  claws_mail_python_init();
+
+  /* load menu options */
+  python_menu_init();
+
+  run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_STARTUP, NULL);
+
+  debug_print("Python plugin loaded\n");
+
+  return 0;
+}
+
+gboolean plugin_done(void)
+{
+  hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);
+
+  run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_SHUTDOWN, NULL);
+
+  python_menu_done();
+
+  if(python_console) {
+    gtk_widget_destroy(python_console);
+    python_console = NULL;
+  }
+
+  /* finialize python interpreter */
+  Py_Finalize();
+
+  debug_print("Python plugin done and unloaded.\n");
+  return FALSE;
+}
+
+const gchar *plugin_name(void)
+{
+  return _("Python");
+}
+
+const gchar *plugin_desc(void)
+{
+  return _("This plugin provides Python integration features.\n"
+           "\nFor the most up-to-date API documentation, type\n"
+           "\n help(clawsmail)\n"
+           "\nin the interactive Python console under Tools -> Show Python console.\n"
+           "\nThe source distribution of this plugin comes with various example scripts "
+           "in the \"examples\" subdirectory. If you wrote a script that you would be "
+           "interested in sharing, feel free to send it to me to have it considered "
+           "for inclusion in the examples.\n"
+           "\nFeedback to <berndth@gmx.de> is welcome.");
+}
+
+const gchar *plugin_type(void)
+{
+  return "GTK2";
+}
+
+const gchar *plugin_licence(void)
+{
+  return "GPL3+";
+}
+
+const gchar *plugin_version(void)
+{
+  return VERSION;
+}
+
+struct PluginFeature *plugin_provides(void)
+{
+  static struct PluginFeature features[] =
+    { {PLUGIN_UTILITY, N_("Python integration")},
+      {PLUGIN_NOTHING, NULL}};
+  return features;
+}
index 333abd319bfb235ce8fa0b620499f2c18809e647..0c75e6c68935e061168c8c3d4046d62d56272a1e 100644 (file)
@@ -215,6 +215,8 @@ ENCHANT_LIBS = @ENCHANT_LIBS@
 EXEEXT = @EXEEXT@
 EXTRA_VERSION = @EXTRA_VERSION@
 FGREP = @FGREP@
 EXEEXT = @EXEEXT@
 EXTRA_VERSION = @EXTRA_VERSION@
 FGREP = @FGREP@
+GDATA_CFLAGS = @GDATA_CFLAGS@
+GDATA_LIBS = @GDATA_LIBS@
 GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
 GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
 GLIB_CFLAGS = @GLIB_CFLAGS@
 GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
 GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
 GLIB_CFLAGS = @GLIB_CFLAGS@
@@ -300,11 +302,27 @@ PACKAGE_VERSION = @PACKAGE_VERSION@
 PASSCRYPT_KEY = @PASSCRYPT_KEY@
 PATH_SEPARATOR = @PATH_SEPARATOR@
 PERL = @PERL@
 PASSCRYPT_KEY = @PASSCRYPT_KEY@
 PATH_SEPARATOR = @PATH_SEPARATOR@
 PERL = @PERL@
+PERL_CFLAGS = @PERL_CFLAGS@
+PERL_LDFLAGS = @PERL_LDFLAGS@
+PERL_PATH = @PERL_PATH@
 PKG_CONFIG = @PKG_CONFIG@
 PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
 PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
 PKG_CONFIG = @PKG_CONFIG@
 PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
 PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POPPLER_CFLAGS = @POPPLER_CFLAGS@
+POPPLER_LIBS = @POPPLER_LIBS@
 POSUB = @POSUB@
 PTHREAD_LIBS = @PTHREAD_LIBS@
 POSUB = @POSUB@
 PTHREAD_LIBS = @PTHREAD_LIBS@
+PYGTK_CFLAGS = @PYGTK_CFLAGS@
+PYGTK_LIBS = @PYGTK_LIBS@
+PYTHON = @PYTHON@
+PYTHON_CFLAGS = @PYTHON_CFLAGS@
+PYTHON_CONFIG = @PYTHON_CONFIG@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_LIBS = @PYTHON_LIBS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_SHARED_LIB = @PYTHON_SHARED_LIB@
+PYTHON_VERSION = @PYTHON_VERSION@
 RANLIB = @RANLIB@
 RC = @RC@
 SED = @SED@
 RANLIB = @RANLIB@
 RC = @RC@
 SED = @SED@
@@ -370,10 +388,15 @@ manualdir = @manualdir@
 mkdir_p = @mkdir_p@
 oldincludedir = @oldincludedir@
 pdfdir = @pdfdir@
 mkdir_p = @mkdir_p@
 oldincludedir = @oldincludedir@
 pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
 prefix = @prefix@
 program_transform_name = @program_transform_name@
 psdir = @psdir@
 prefix = @prefix@
 program_transform_name = @program_transform_name@
 psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
 sbindir = @sbindir@
 sbindir = @sbindir@
+sedpath = @sedpath@
 sharedstatedir = @sharedstatedir@
 srcdir = @srcdir@
 sysconfdir = @sysconfdir@
 sharedstatedir = @sharedstatedir@
 srcdir = @srcdir@
 sysconfdir = @sysconfdir@