talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

commit abf3a16d4009378957934790e69619e292642dfa
parent 2c79a205e4b3c1134b237adc1e6460b309381ba7
Author: Colin Leroy <colin@colino.net>
Date:   Sun, 17 Feb 2013 21:22:03 +0000

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.

Diffstat:
MChangeLog | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MPATCHSETS | 1+
Mconfigure.ac | 248++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/plugins/Makefile.am | 45+++++++++++++++++++++++++++++++++++++++++++++
Msrc/plugins/archive/Makefile.am | 3---
Asrc/plugins/clamd/Makefile.am | 30++++++++++++++++++++++++++++++
Asrc/plugins/clamd/clamav_plugin.c | 344+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/clamd/clamav_plugin.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/clamd/clamav_plugin_gtk.c | 569+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/clamd/libclamd/Makefile.am | 20++++++++++++++++++++
Asrc/plugins/clamd/libclamd/clamd-plugin.c | 689+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/clamd/libclamd/clamd-plugin.h | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/plugins/clamd/placeholder.txt | 1-
Asrc/plugins/fetchinfo/Makefile.am | 23+++++++++++++++++++++++
Asrc/plugins/fetchinfo/fetchinfo_plugin.c | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/fetchinfo/fetchinfo_plugin.h | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/fetchinfo/fetchinfo_plugin_gtk.c | 198+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/plugins/fetchinfo/placeholder.txt | 1-
Asrc/plugins/gdata/Makefile.am | 30++++++++++++++++++++++++++++++
Asrc/plugins/gdata/cm_gdata_contacts.c | 508+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/gdata/cm_gdata_contacts.h | 28++++++++++++++++++++++++++++
Asrc/plugins/gdata/cm_gdata_prefs.c | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/gdata/cm_gdata_prefs.h | 36++++++++++++++++++++++++++++++++++++
Asrc/plugins/gdata/gdata_plugin.c | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/gdata/gdata_plugin.h | 23+++++++++++++++++++++++
Dsrc/plugins/gdata/placeholder.txt | 1-
Dsrc/plugins/geolocation/placeholder.txt | 1-
Dsrc/plugins/gtkhtml2_viewer/placeholder.txt | 1-
Asrc/plugins/mailmbox/Makefile.am | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/carray.c | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/carray.h | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/chash.c | 395+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/chash.h | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/clist.c | 266+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/clist.h | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailimf.c | 7585+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailimf.h | 345+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailimf_types.c | 868+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailimf_types.h | 797+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailimf_types_helper.c | 1636+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailimf_types_helper.h | 370+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailimf_write.c | 2021+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailimf_write.h | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/maillock.c | 284+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/maillock.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailmbox.c | 1473+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailmbox.h | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailmbox_folder.c | 1142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailmbox_folder.h | 39+++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailmbox_gtk.deps | 1+
Asrc/plugins/mailmbox/mailmbox_parse.c | 624+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailmbox_parse.h | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailmbox_types.c | 250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mailmbox_types.h | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mmapstring.c | 525+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/mmapstring.h | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/plugins/mailmbox/placeholder.txt | 1-
Asrc/plugins/mailmbox/plugin.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/plugin_gtk.c | 455+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/mailmbox/plugin_gtk.h | 30++++++++++++++++++++++++++++++
Asrc/plugins/newmail/Makefile.am | 19+++++++++++++++++++
Asrc/plugins/newmail/newmail.c | 168+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/plugins/newmail/placeholder.txt | 1-
Asrc/plugins/notification/Makefile.am | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/claws.def | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_banner.c | 503+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_banner.h | 32++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_command.c | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_command.h | 33+++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_core.c | 699+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_core.h | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_foldercheck.c | 1002+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_foldercheck.h | 32++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_hotkeys.c | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_hotkeys.h | 31+++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_indicator.c | 174+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_indicator.h | 31+++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_lcdproc.c | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_lcdproc.h | 34++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_pixbuf.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_pixbuf.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_plugin.c | 479+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_plugin.h | 29+++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_popup.c | 677+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_popup.h | 33+++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_prefs.c | 1952+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_prefs.h | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_trayicon.c | 948+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/notification_trayicon.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/plugins/notification/placeholder.txt | 1-
Asrc/plugins/notification/plugin.def | 10++++++++++
Asrc/plugins/notification/raw_claws_mail_logo_64x64.h | 684+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/notification/version.rc | 36++++++++++++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/Makefile.am | 26++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/doc_index.xpm | 21+++++++++++++++++++++
Asrc/plugins/pdf_viewer/doc_index_close.xpm | 19+++++++++++++++++++
Asrc/plugins/pdf_viewer/doc_info.xpm | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/first_arrow.xpm | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/last_arrow.xpm | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/left_arrow.xpm | 44++++++++++++++++++++++++++++++++++++++++++++
Dsrc/plugins/pdf_viewer/placeholder.txt | 1-
Asrc/plugins/pdf_viewer/poppler_viewer.c | 2106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/poppler_viewer.h | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/right_arrow.xpm | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/rotate_left.xpm | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/rotate_right.xpm | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/zoom_fit.xpm | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/zoom_in.xpm | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/zoom_out.xpm | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/pdf_viewer/zoom_width.xpm | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/perl/Makefile.am | 25+++++++++++++++++++++++++
Asrc/plugins/perl/perl_gtk.c | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/perl/perl_gtk.h | 28++++++++++++++++++++++++++++
Asrc/plugins/perl/perl_plugin.c | 2400+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/perl/perl_plugin.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/plugins/perl/placeholder.txt | 1-
Asrc/plugins/python/Makefile.am | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/clawsmailmodule.c | 764+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/clawsmailmodule.h | 33+++++++++++++++++++++++++++++++++
Asrc/plugins/python/composewindowtype.c | 617+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/composewindowtype.h | 33+++++++++++++++++++++++++++++++++
Asrc/plugins/python/foldertype.c | 271+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/foldertype.h | 37+++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/messageinfotype.c | 396+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/messageinfotype.h | 35+++++++++++++++++++++++++++++++++++
Asrc/plugins/python/nodetype.c | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/nodetype.h | 31+++++++++++++++++++++++++++++++
Dsrc/plugins/python/placeholder.txt | 1-
Asrc/plugins/python/python-hooks.c | 290+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/python-hooks.h | 38++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/python-shell.c | 438+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/python-shell.h | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/python/python_plugin.c | 639+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/plugins/vcalendar/Makefile.in | 23+++++++++++++++++++++++
134 files changed, 42232 insertions(+), 15 deletions(-)

diff --git 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 diff --git a/PATCHSETS b/PATCHSETS @@ -4572,3 +4572,4 @@ ( 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 diff --git a/configure.ac b/configure.ac @@ -12,7 +12,7 @@ MINOR_VERSION=9 MICRO_VERSION=0 INTERFACE_AGE=0 BINARY_AGE=0 -EXTRA_VERSION=74 +EXTRA_VERSION=75 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_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) 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_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) 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" + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) 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_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) 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_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) 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_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) 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" + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) 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) +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, @@ -1214,9 +1451,18 @@ src/plugins/att_remover/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/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 diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am @@ -26,6 +26,10 @@ if BUILD_BSFILTER_PLUGIN bsfilter_dir = bsfilter endif +if BUILD_CLAMD_PLUGIN +clamd_dir = clamd +endif + if BUILD_DEMO_PLUGIN demo_dir = demo endif @@ -38,6 +42,38 @@ if BUILD_FANCY_PLUGIN 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 @@ -75,9 +111,18 @@ SUBDIRS = \ $(attachwarner_dir) \ $(bogofilter_dir) \ $(bsfilter_dir) \ + $(clamd_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) \ diff --git a/src/plugins/archive/Makefile.am b/src/plugins/archive/Makefile.am @@ -1,8 +1,5 @@ plugindir = $(pkglibdir)/plugins -INCLUDES = @GLIB_CFLAGS@ \ - -I$(top_srcdir) -I$(top_builddir) - plugin_LTLIBRARIES = archive.la archive_la_SOURCES = \ diff --git a/src/plugins/clamd/Makefile.am b/src/plugins/clamd/Makefile.am @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -1 +0,0 @@ -Placeholder diff --git a/src/plugins/fetchinfo/Makefile.am b/src/plugins/fetchinfo/Makefile.am @@ -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 @@ -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 @@ -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 @@ -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 @@ -1 +0,0 @@ -Placeholder diff --git a/src/plugins/gdata/Makefile.am b/src/plugins/gdata/Makefile.am @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -1 +0,0 @@ -Placeholder diff --git a/src/plugins/geolocation/placeholder.txt b/src/plugins/geolocation/placeholder.txt @@ -1 +0,0 @@ -Placeholder diff --git a/src/plugins/gtkhtml2_viewer/placeholder.txt b/src/plugins/gtkhtml2_viewer/placeholder.txt @@ -1 +0,0 @@ -Placeholder diff --git a/src/plugins/mailmbox/Makefile.am b/src/plugins/mailmbox/Makefile.am @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -0,0 +1 @@ +mailmbox diff --git a/src/plugins/mailmbox/mailmbox_parse.c b/src/plugins/mailmbox/mailmbox_parse.c @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -1 +0,0 @@ -Placeholder diff --git a/src/plugins/mailmbox/plugin.c b/src/plugins/mailmbox/plugin.c @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -1 +0,0 @@ -Placeholder diff --git a/src/plugins/notification/Makefile.am b/src/plugins/notification/Makefile.am @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -1 +0,0 @@ -Placeholder diff --git a/src/plugins/notification/plugin.def b/src/plugins/notification/plugin.def @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -1 +0,0 @@ -Placeholder diff --git a/src/plugins/pdf_viewer/poppler_viewer.c b/src/plugins/pdf_viewer/poppler_viewer.c @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -1 +0,0 @@ -Placeholder diff --git a/src/plugins/python/Makefile.am b/src/plugins/python/Makefile.am @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -1 +0,0 @@ -Placeholder diff --git a/src/plugins/python/python-hooks.c b/src/plugins/python/python-hooks.c @@ -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 @@ -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 @@ -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 @@ -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 @@ -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; +} diff --git a/src/plugins/vcalendar/Makefile.in b/src/plugins/vcalendar/Makefile.in @@ -215,6 +215,8 @@ ENCHANT_LIBS = @ENCHANT_LIBS@ 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@ @@ -300,11 +302,27 @@ PACKAGE_VERSION = @PACKAGE_VERSION@ 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@ +POPPLER_CFLAGS = @POPPLER_CFLAGS@ +POPPLER_LIBS = @POPPLER_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@ @@ -370,10 +388,15 @@ manualdir = @manualdir@ mkdir_p = @mkdir_p@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ sbindir = @sbindir@ +sedpath = @sedpath@ sharedstatedir = @sharedstatedir@ srcdir = @srcdir@ sysconfdir = @sysconfdir@