From 0b3cbf57875fd692e4ba0b336fefa4bee1ed00dc Mon Sep 17 00:00:00 2001 From: Simeon Simeonov Date: Mon, 26 Feb 2018 11:23:00 +0100 Subject: Initial commit for sylpheed 3.7.0 --- libsylph/Makefile.am | 118 + libsylph/Makefile.in | 881 ++++++ libsylph/account.c | 491 ++++ libsylph/account.h | 70 + libsylph/base64.c | 168 ++ libsylph/base64.h | 46 + libsylph/codeconv.c | 2856 ++++++++++++++++++++ libsylph/codeconv.h | 267 ++ libsylph/customheader.c | 233 ++ libsylph/customheader.h | 45 + libsylph/defs.h | 134 + libsylph/displayheader.c | 59 + libsylph/displayheader.h | 37 + libsylph/enums.h | 66 + libsylph/filter.c | 1951 ++++++++++++++ libsylph/filter.h | 247 ++ libsylph/folder.c | 1994 ++++++++++++++ libsylph/folder.h | 475 ++++ libsylph/html.c | 791 ++++++ libsylph/html.h | 89 + libsylph/imap.c | 5215 ++++++++++++++++++++++++++++++++++++ libsylph/imap.h | 130 + libsylph/libsylph-0.def | 714 +++++ libsylph/mbox.c | 572 ++++ libsylph/mbox.h | 56 + libsylph/md5.c | 637 +++++ libsylph/md5.h | 61 + libsylph/md5_hmac.c | 134 + libsylph/md5_hmac.h | 33 + libsylph/mh.c | 1898 +++++++++++++ libsylph/mh.h | 38 + libsylph/news.c | 1110 ++++++++ libsylph/news.h | 60 + libsylph/nntp.c | 471 ++++ libsylph/nntp.h | 123 + libsylph/pop.c | 927 +++++++ libsylph/pop.h | 157 ++ libsylph/prefs.c | 588 ++++ libsylph/prefs.h | 80 + libsylph/prefs_account.c | 296 ++ libsylph/prefs_account.h | 205 ++ libsylph/prefs_common.c | 634 +++++ libsylph/prefs_common.h | 375 +++ libsylph/procheader.c | 979 +++++++ libsylph/procheader.h | 101 + libsylph/procmime.c | 1955 ++++++++++++++ libsylph/procmime.h | 213 ++ libsylph/procmsg.c | 2446 +++++++++++++++++ libsylph/procmsg.h | 355 +++ libsylph/quoted-printable.c | 232 ++ libsylph/quoted-printable.h | 36 + libsylph/recv.c | 245 ++ libsylph/recv.h | 49 + libsylph/session.c | 1320 +++++++++ libsylph/session.h | 248 ++ libsylph/smtp.c | 617 +++++ libsylph/smtp.h | 116 + libsylph/socket.c | 2179 +++++++++++++++ libsylph/socket.h | 163 ++ libsylph/socks.c | 267 ++ libsylph/socks.h | 59 + libsylph/ssl.c | 405 +++ libsylph/ssl.h | 65 + libsylph/ssl_hostname_validation.c | 282 ++ libsylph/ssl_hostname_validation.h | 66 + libsylph/stringtable.c | 163 ++ libsylph/stringtable.h | 38 + libsylph/syl-marshal.c | 131 + libsylph/syl-marshal.h | 34 + libsylph/syl-marshal.list | 4 + libsylph/sylmain.c | 342 +++ libsylph/sylmain.h | 57 + libsylph/unmime.c | 135 + libsylph/unmime.h | 27 + libsylph/utils.c | 4849 +++++++++++++++++++++++++++++++++ libsylph/utils.h | 599 +++++ libsylph/uuencode.c | 101 + libsylph/uuencode.h | 24 + libsylph/virtual.c | 603 +++++ libsylph/virtual.h | 38 + libsylph/xml.c | 688 +++++ libsylph/xml.h | 109 + 82 files changed, 45872 insertions(+) create mode 100644 libsylph/Makefile.am create mode 100644 libsylph/Makefile.in create mode 100644 libsylph/account.c create mode 100644 libsylph/account.h create mode 100644 libsylph/base64.c create mode 100644 libsylph/base64.h create mode 100644 libsylph/codeconv.c create mode 100644 libsylph/codeconv.h create mode 100644 libsylph/customheader.c create mode 100644 libsylph/customheader.h create mode 100644 libsylph/defs.h create mode 100644 libsylph/displayheader.c create mode 100644 libsylph/displayheader.h create mode 100644 libsylph/enums.h create mode 100644 libsylph/filter.c create mode 100644 libsylph/filter.h create mode 100644 libsylph/folder.c create mode 100644 libsylph/folder.h create mode 100644 libsylph/html.c create mode 100644 libsylph/html.h create mode 100644 libsylph/imap.c create mode 100644 libsylph/imap.h create mode 100644 libsylph/libsylph-0.def create mode 100644 libsylph/mbox.c create mode 100644 libsylph/mbox.h create mode 100644 libsylph/md5.c create mode 100644 libsylph/md5.h create mode 100644 libsylph/md5_hmac.c create mode 100644 libsylph/md5_hmac.h create mode 100644 libsylph/mh.c create mode 100644 libsylph/mh.h create mode 100644 libsylph/news.c create mode 100644 libsylph/news.h create mode 100644 libsylph/nntp.c create mode 100644 libsylph/nntp.h create mode 100644 libsylph/pop.c create mode 100644 libsylph/pop.h create mode 100644 libsylph/prefs.c create mode 100644 libsylph/prefs.h create mode 100644 libsylph/prefs_account.c create mode 100644 libsylph/prefs_account.h create mode 100644 libsylph/prefs_common.c create mode 100644 libsylph/prefs_common.h create mode 100644 libsylph/procheader.c create mode 100644 libsylph/procheader.h create mode 100644 libsylph/procmime.c create mode 100644 libsylph/procmime.h create mode 100644 libsylph/procmsg.c create mode 100644 libsylph/procmsg.h create mode 100644 libsylph/quoted-printable.c create mode 100644 libsylph/quoted-printable.h create mode 100644 libsylph/recv.c create mode 100644 libsylph/recv.h create mode 100644 libsylph/session.c create mode 100644 libsylph/session.h create mode 100644 libsylph/smtp.c create mode 100644 libsylph/smtp.h create mode 100644 libsylph/socket.c create mode 100644 libsylph/socket.h create mode 100644 libsylph/socks.c create mode 100644 libsylph/socks.h create mode 100644 libsylph/ssl.c create mode 100644 libsylph/ssl.h create mode 100644 libsylph/ssl_hostname_validation.c create mode 100644 libsylph/ssl_hostname_validation.h create mode 100644 libsylph/stringtable.c create mode 100644 libsylph/stringtable.h create mode 100644 libsylph/syl-marshal.c create mode 100644 libsylph/syl-marshal.h create mode 100644 libsylph/syl-marshal.list create mode 100644 libsylph/sylmain.c create mode 100644 libsylph/sylmain.h create mode 100644 libsylph/unmime.c create mode 100644 libsylph/unmime.h create mode 100644 libsylph/utils.c create mode 100644 libsylph/utils.h create mode 100644 libsylph/uuencode.c create mode 100644 libsylph/uuencode.h create mode 100644 libsylph/virtual.c create mode 100644 libsylph/virtual.h create mode 100644 libsylph/xml.c create mode 100644 libsylph/xml.h (limited to 'libsylph') diff --git a/libsylph/Makefile.am b/libsylph/Makefile.am new file mode 100644 index 0000000..8f2b76b --- /dev/null +++ b/libsylph/Makefile.am @@ -0,0 +1,118 @@ + +AM_CPPFLAGS = \ + -DG_LOG_DOMAIN=\"LibSylph\" \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -DLOCALEDIR=\""$(localedir)"\" + +INCLUDES = -I$(top_srcdir) $(GLIB_CFLAGS) $(LIBSYLPH_CFLAGS) + +lib_LTLIBRARIES = libsylph-0.la +#noinst_LTLIBRARIES = libsylph.la + +libsylph_0_la_SOURCES = \ + account.c \ + base64.c \ + codeconv.c \ + customheader.c \ + displayheader.c \ + filter.c \ + folder.c \ + html.c \ + imap.c \ + mbox.c \ + md5.c \ + md5_hmac.c \ + mh.c \ + news.c \ + nntp.c \ + pop.c \ + prefs.c \ + prefs_account.c \ + prefs_common.c \ + procheader.c \ + procmime.c \ + procmsg.c \ + quoted-printable.c \ + recv.c \ + session.c \ + smtp.c \ + socket.c \ + socks.c \ + ssl.c \ + ssl_hostname_validation.c \ + stringtable.c \ + sylmain.c \ + unmime.c \ + utils.c \ + uuencode.c \ + virtual.c \ + xml.c \ + syl-marshal.c + +libsylph_0includedir=$(includedir)/sylpheed/sylph +libsylph_0include_HEADERS = \ + defs.h \ + enums.h \ + account.h \ + base64.h \ + codeconv.h \ + customheader.h \ + displayheader.h \ + filter.h \ + folder.h \ + html.h \ + imap.h \ + mbox.h \ + md5.h \ + md5_hmac.h \ + mh.h \ + news.h \ + nntp.h \ + pop.h \ + prefs.h \ + prefs_account.h \ + prefs_common.h \ + procheader.h \ + procmime.h \ + procmsg.h \ + quoted-printable.h \ + recv.h \ + session.h \ + smtp.h \ + socket.h \ + socks.h \ + ssl.h \ + ssl_hostname_validation.h \ + stringtable.h \ + sylmain.h \ + unmime.h \ + utils.h \ + uuencode.h \ + virtual.h \ + xml.h \ + syl-marshal.h + +BUILT_SOURCES = \ + syl-marshal.c \ + syl-marshal.h + +EXTRA_DIST = \ + syl-marshal.list \ + libsylph-0.def + +if NATIVE_WIN32 +no_undefined = -no-undefined +export_symbols = -export-symbols libsylph-0.def +endif + +libsylph_0_la_LDFLAGS = \ + -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \ + -export-dynamic $(no_undefined) $(export_symbols) + +libsylph_0_la_LIBADD = $(GLIB_LIBS) $(LIBICONV) $(LIBSYLPH_LIBS) + +syl-marshal.h: syl-marshal.list + $(GLIB_GENMARSHAL) $< --header --prefix=syl_marshal > $@ + +syl-marshal.c: syl-marshal.list + $(GLIB_GENMARSHAL) $< --body --prefix=syl_marshal > $@ diff --git a/libsylph/Makefile.in b/libsylph/Makefile.in new file mode 100644 index 0000000..6e3c999 --- /dev/null +++ b/libsylph/Makefile.in @@ -0,0 +1,881 @@ +# Makefile.in generated by automake 1.14.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = libsylph +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/mkinstalldirs $(top_srcdir)/depcomp \ + $(libsylph_0include_HEADERS) +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/ac/aclocal-include.m4 \ + $(top_srcdir)/ac/gnupg-check-typedef.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(libdir)" \ + "$(DESTDIR)$(libsylph_0includedir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libsylph_0_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am_libsylph_0_la_OBJECTS = account.lo base64.lo codeconv.lo \ + customheader.lo displayheader.lo filter.lo folder.lo html.lo \ + imap.lo mbox.lo md5.lo md5_hmac.lo mh.lo news.lo nntp.lo \ + pop.lo prefs.lo prefs_account.lo prefs_common.lo procheader.lo \ + procmime.lo procmsg.lo quoted-printable.lo recv.lo session.lo \ + smtp.lo socket.lo socks.lo ssl.lo ssl_hostname_validation.lo \ + stringtable.lo sylmain.lo unmime.lo utils.lo uuencode.lo \ + virtual.lo xml.lo syl-marshal.lo +libsylph_0_la_OBJECTS = $(am_libsylph_0_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libsylph_0_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(libsylph_0_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libsylph_0_la_SOURCES) +DIST_SOURCES = $(libsylph_0_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(libsylph_0include_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ALLOCA = @ALLOCA@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BUILD_REVISION = @BUILD_REVISION@ +CATALOGS = @CATALOGS@ +CATOBJEXT = @CATOBJEXT@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DEVEL_VERSION = @DEVEL_VERSION@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +EXTRA_VERSION = @EXTRA_VERSION@ +FGREP = @FGREP@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GMOFILES = @GMOFILES@ +GMSGFMT = @GMSGFMT@ +GOBJECT_QUERY = @GOBJECT_QUERY@ +GPGME_CFLAGS = @GPGME_CFLAGS@ +GPGME_CONFIG = @GPGME_CONFIG@ +GPGME_LIBS = @GPGME_LIBS@ +GREP = @GREP@ +GTK_CFLAGS = @GTK_CFLAGS@ +GTK_LIBS = @GTK_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INSTOBJEXT = @INSTOBJEXT@ +INTLLIBS = @INTLLIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSYLPH_CFLAGS = @LIBSYLPH_CFLAGS@ +LIBSYLPH_LIBS = @LIBSYLPH_LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_AGE = @LT_AGE@ +LT_CURRENT = @LT_CURRENT@ +LT_RELEASE = @LT_RELEASE@ +LT_REVISION = @LT_REVISION@ +MAINT = @MAINT@ +MAJOR_VERSION = @MAJOR_VERSION@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MICRO_VERSION = @MICRO_VERSION@ +MINOR_VERSION = @MINOR_VERSION@ +MKDIR_P = @MKDIR_P@ +MKINSTALLDIRS = @MKINSTALLDIRS@ +MSGFMT = @MSGFMT@ +MSGFMT_OPTS = @MSGFMT_OPTS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_CFLAGS = @OPENSSL_CFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POFILES = @POFILES@ +POSUB = @POSUB@ +PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@ +PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYLPHEED_LIBS = @SYLPHEED_LIBS@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +WINDRES = @WINDRES@ +XGETTEXT = @XGETTEXT@ +YACC = @YACC@ +YFLAGS = @YFLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +faqdir = @faqdir@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +manualdir = @manualdir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +plugindir = @plugindir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +themedir = @themedir@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = \ + -DG_LOG_DOMAIN=\"LibSylph\" \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -DLOCALEDIR=\""$(localedir)"\" + +INCLUDES = -I$(top_srcdir) $(GLIB_CFLAGS) $(LIBSYLPH_CFLAGS) +lib_LTLIBRARIES = libsylph-0.la +#noinst_LTLIBRARIES = libsylph.la +libsylph_0_la_SOURCES = \ + account.c \ + base64.c \ + codeconv.c \ + customheader.c \ + displayheader.c \ + filter.c \ + folder.c \ + html.c \ + imap.c \ + mbox.c \ + md5.c \ + md5_hmac.c \ + mh.c \ + news.c \ + nntp.c \ + pop.c \ + prefs.c \ + prefs_account.c \ + prefs_common.c \ + procheader.c \ + procmime.c \ + procmsg.c \ + quoted-printable.c \ + recv.c \ + session.c \ + smtp.c \ + socket.c \ + socks.c \ + ssl.c \ + ssl_hostname_validation.c \ + stringtable.c \ + sylmain.c \ + unmime.c \ + utils.c \ + uuencode.c \ + virtual.c \ + xml.c \ + syl-marshal.c + +libsylph_0includedir = $(includedir)/sylpheed/sylph +libsylph_0include_HEADERS = \ + defs.h \ + enums.h \ + account.h \ + base64.h \ + codeconv.h \ + customheader.h \ + displayheader.h \ + filter.h \ + folder.h \ + html.h \ + imap.h \ + mbox.h \ + md5.h \ + md5_hmac.h \ + mh.h \ + news.h \ + nntp.h \ + pop.h \ + prefs.h \ + prefs_account.h \ + prefs_common.h \ + procheader.h \ + procmime.h \ + procmsg.h \ + quoted-printable.h \ + recv.h \ + session.h \ + smtp.h \ + socket.h \ + socks.h \ + ssl.h \ + ssl_hostname_validation.h \ + stringtable.h \ + sylmain.h \ + unmime.h \ + utils.h \ + uuencode.h \ + virtual.h \ + xml.h \ + syl-marshal.h + +BUILT_SOURCES = \ + syl-marshal.c \ + syl-marshal.h + +EXTRA_DIST = \ + syl-marshal.list \ + libsylph-0.def + +@NATIVE_WIN32_TRUE@no_undefined = -no-undefined +@NATIVE_WIN32_TRUE@export_symbols = -export-symbols libsylph-0.def +libsylph_0_la_LDFLAGS = \ + -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \ + -export-dynamic $(no_undefined) $(export_symbols) + +libsylph_0_la_LIBADD = $(GLIB_LIBS) $(LIBICONV) $(LIBSYLPH_LIBS) +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign libsylph/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign libsylph/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libsylph-0.la: $(libsylph_0_la_OBJECTS) $(libsylph_0_la_DEPENDENCIES) $(EXTRA_libsylph_0_la_DEPENDENCIES) + $(AM_V_CCLD)$(libsylph_0_la_LINK) -rpath $(libdir) $(libsylph_0_la_OBJECTS) $(libsylph_0_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/account.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base64.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/codeconv.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/customheader.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/displayheader.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/folder.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/html.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mbox.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5_hmac.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mh.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/news.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nntp.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pop.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/prefs.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/prefs_account.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/prefs_common.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/procheader.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/procmime.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/procmsg.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quoted-printable.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/recv.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socks.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ssl.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ssl_hostname_validation.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stringtable.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/syl-marshal.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sylmain.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unmime.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/uuencode.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xml.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-libsylph_0includeHEADERS: $(libsylph_0include_HEADERS) + @$(NORMAL_INSTALL) + @list='$(libsylph_0include_HEADERS)'; test -n "$(libsylph_0includedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libsylph_0includedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libsylph_0includedir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libsylph_0includedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libsylph_0includedir)" || exit $$?; \ + done + +uninstall-libsylph_0includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(libsylph_0include_HEADERS)'; test -n "$(libsylph_0includedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(libsylph_0includedir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libsylph_0includedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-libsylph_0includeHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-libLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-libLTLIBRARIES \ + uninstall-libsylph_0includeHEADERS + +.MAKE: all check install install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-libLTLIBRARIES \ + install-libsylph_0includeHEADERS install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \ + uninstall-libsylph_0includeHEADERS + + +syl-marshal.h: syl-marshal.list + $(GLIB_GENMARSHAL) $< --header --prefix=syl_marshal > $@ + +syl-marshal.c: syl-marshal.list + $(GLIB_GENMARSHAL) $< --body --prefix=syl_marshal > $@ + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/libsylph/account.c b/libsylph/account.c new file mode 100644 index 0000000..2431bf3 --- /dev/null +++ b/libsylph/account.c @@ -0,0 +1,491 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2009 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include + +#include "folder.h" +#include "account.h" +#include "prefs.h" +#include "prefs_account.h" +#include "procmsg.h" +#include "procheader.h" +#include "utils.h" +#include "sylmain.h" + +PrefsAccount *cur_account; + +static GList *account_list = NULL; + +GHashTable *address_table; + + +void account_read_config_all(void) +{ + GSList *ac_label_list = NULL, *cur; + gchar *rcpath; + FILE *fp; + gchar buf[PREFSBUFSIZE]; + PrefsAccount *ac_prefs; + + debug_print(_("Reading all config for each account...\n")); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACCOUNT_RC, NULL); + if ((fp = g_fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + g_free(rcpath); + return; + } + g_free(rcpath); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + if (!strncmp(buf, "[Account: ", 10)) { + strretchomp(buf); + memmove(buf, buf + 1, strlen(buf)); + buf[strlen(buf) - 1] = '\0'; + debug_print("Found label: %s\n", buf); + ac_label_list = g_slist_append(ac_label_list, + g_strdup(buf)); + } + } + fclose(fp); + + /* read config data from file */ + cur_account = NULL; + for (cur = ac_label_list; cur != NULL; cur = cur->next) { + ac_prefs = prefs_account_new(); + prefs_account_read_config(ac_prefs, (gchar *)cur->data); + account_list = g_list_append(account_list, ac_prefs); + if (ac_prefs->is_default) + cur_account = ac_prefs; + } + /* if default is not set, assume first account as default */ + if (!cur_account && account_list) { + ac_prefs = (PrefsAccount *)account_list->data; + account_set_as_default(ac_prefs); + cur_account = ac_prefs; + } + + while (ac_label_list) { + g_free(ac_label_list->data); + ac_label_list = g_slist_remove(ac_label_list, + ac_label_list->data); + } +} + +void account_write_config_all(void) +{ + prefs_account_write_config_all(account_list); +} + +PrefsAccount *account_get_current_account(void) +{ + return cur_account; +} + +PrefsAccount *account_find_from_smtp_server(const gchar *address, + const gchar *smtp_server) +{ + GList *cur; + PrefsAccount *ac; + + g_return_val_if_fail(address != NULL, NULL); + g_return_val_if_fail(smtp_server != NULL, NULL); + + for (cur = account_list; cur != NULL; cur = cur->next) { + ac = (PrefsAccount *)cur->data; + if (!strcmp2(address, ac->address) && + !strcmp2(smtp_server, ac->smtp_server)) + return ac; + } + + return NULL; +} + +/* + * account_find_from_address: + * @address: Email address string. + * + * Find a mail (not news) account with the specified email address. + * + * Return value: The found account, or NULL if not found. + */ +PrefsAccount *account_find_from_address(const gchar *address) +{ + GList *cur; + PrefsAccount *ac; + + g_return_val_if_fail(address != NULL, NULL); + + for (cur = account_list; cur != NULL; cur = cur->next) { + ac = (PrefsAccount *)cur->data; + if (ac->protocol != A_NNTP && ac->address && + strcasestr(address, ac->address) != NULL) + return ac; + } + + return NULL; +} + +PrefsAccount *account_find_from_id(gint id) +{ + GList *cur; + PrefsAccount *ac; + + for (cur = account_list; cur != NULL; cur = cur->next) { + ac = (PrefsAccount *)cur->data; + if (id == ac->account_id) + return ac; + } + + return NULL; +} + +PrefsAccount *account_find_from_item(FolderItem *item) +{ + PrefsAccount *ac; + + g_return_val_if_fail(item != NULL, NULL); + + ac = account_find_from_item_property(item); + if (!ac) + ac = item->folder->account; + + return ac; +} + +PrefsAccount *account_find_from_item_property(FolderItem *item) +{ + PrefsAccount *ac; + + g_return_val_if_fail(item != NULL, NULL); + + ac = item->account; + if (!ac) { + FolderItem *cur_item = item->parent; + while (cur_item != NULL) { + if (cur_item->account && cur_item->ac_apply_sub) { + ac = cur_item->account; + break; + } + cur_item = cur_item->parent; + } + } + + return ac; +} + +PrefsAccount *account_find_from_message_file(const gchar *file) +{ + static HeaderEntry hentry[] = {{"From:", NULL, FALSE}, + {"X-Sylpheed-Account-Id:", NULL, FALSE}, + {"AID:", NULL, FALSE}, + {NULL, NULL, FALSE}}; + + enum + { + H_FROM = 0, + H_X_SYLPHEED_ACCOUNT_ID = 1, + H_AID = 2 + }; + + PrefsAccount *ac = NULL; + FILE *fp; + gchar *str; + gchar buf[BUFFSIZE]; + gint hnum; + + g_return_val_if_fail(file != NULL, NULL); + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return NULL; + } + + while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry)) + != -1) { + str = buf + strlen(hentry[hnum].name); + if (hnum == H_FROM) + ac = account_find_from_address(str); + else if (hnum == H_X_SYLPHEED_ACCOUNT_ID || hnum == H_AID) { + PrefsAccount *tmp_ac; + + tmp_ac = account_find_from_id(atoi(str)); + if (tmp_ac) { + ac = tmp_ac; + break; + } + } + } + + fclose(fp); + return ac; +} + +PrefsAccount *account_find_from_msginfo(MsgInfo *msginfo) +{ + gchar *file; + PrefsAccount *ac; + + file = procmsg_get_message_file(msginfo); + ac = account_find_from_message_file(file); + g_free(file); + + if (!ac && msginfo->folder) + ac = account_find_from_item(msginfo->folder); + + return ac; +} + +gboolean account_address_exist(const gchar *address) +{ + if (!address_table) { + GList *cur; + + address_table = g_hash_table_new(g_str_hash, g_str_equal); + for (cur = account_list; cur != NULL; cur = cur->next) { + PrefsAccount *ac = (PrefsAccount *)cur->data; + + if (ac->address) + g_hash_table_insert(address_table, ac->address, + GINT_TO_POINTER(1)); + } + } + + return (gboolean)g_hash_table_lookup(address_table, address); +} + +void account_foreach(AccountFunc func, gpointer user_data) +{ + GList *cur; + + for (cur = account_list; cur != NULL; cur = cur->next) + if (func((PrefsAccount *)cur->data, user_data) != 0) + return; +} + +GList *account_get_list(void) +{ + return account_list; +} + +void account_list_free(void) +{ + g_list_free(account_list); + account_list = NULL; +} + +void account_append(PrefsAccount *ac_prefs) +{ + account_list = g_list_append(account_list, ac_prefs); + account_updated(); +} + +void account_set_as_default(PrefsAccount *ac_prefs) +{ + PrefsAccount *ap; + GList *cur; + + for (cur = account_list; cur != NULL; cur = cur->next) { + ap = (PrefsAccount *)cur->data; + if (ap->is_default) + ap->is_default = FALSE; + } + + ac_prefs->is_default = TRUE; +} + +PrefsAccount *account_get_default(void) +{ + PrefsAccount *ap; + GList *cur; + + for (cur = account_list; cur != NULL; cur = cur->next) { + ap = (PrefsAccount *)cur->data; + if (ap->is_default) + return ap; + } + + return NULL; +} + +#if 0 +void account_set_missing_folder(void) +{ + PrefsAccount *ap; + GList *cur; + + for (cur = account_list; cur != NULL; cur = cur->next) { + ap = (PrefsAccount *)cur->data; + if ((ap->protocol == A_IMAP4 || ap->protocol == A_NNTP) && + !ap->folder) { + Folder *folder; + + if (ap->protocol == A_IMAP4) { + folder = folder_new(F_IMAP, ap->account_name, + ap->recv_server); + } else { + folder = folder_new(F_NEWS, ap->account_name, + ap->nntp_server); + } + + folder->account = ap; + ap->folder = REMOTE_FOLDER(folder); + folder_add(folder); + if (ap->protocol == A_IMAP4) { + if (main_window_toggle_online_if_offline + (main_window_get())) { + folder->klass->create_tree(folder); + statusbar_pop_all(); + } + } + } + } +} +#endif + +FolderItem *account_get_special_folder(PrefsAccount *ac_prefs, + SpecialFolderItemType type) +{ + FolderItem *item = NULL; + + g_return_val_if_fail(ac_prefs != NULL, NULL); + + switch (type) { + case F_INBOX: + if (ac_prefs->folder) + item = FOLDER(ac_prefs->folder)->inbox; + if (!item) + item = folder_get_default_inbox(); + break; + case F_OUTBOX: + if (ac_prefs->set_sent_folder && ac_prefs->sent_folder) { + item = folder_find_item_from_identifier + (ac_prefs->sent_folder); + } + if (!item) { + if (ac_prefs->folder) + item = FOLDER(ac_prefs->folder)->outbox; + if (!item) + item = folder_get_default_outbox(); + } + break; + case F_DRAFT: + if (ac_prefs->set_draft_folder && ac_prefs->draft_folder) { + item = folder_find_item_from_identifier + (ac_prefs->draft_folder); + } + if (!item) { + if (ac_prefs->folder) + item = FOLDER(ac_prefs->folder)->draft; + if (!item) + item = folder_get_default_draft(); + } + break; + case F_QUEUE: + if (ac_prefs->set_queue_folder && ac_prefs->queue_folder) { + item = folder_find_item_from_identifier + (ac_prefs->queue_folder); + /* only allow queue-type folder */ + if (item && item->stype != F_QUEUE) + item = NULL; + } + if (!item) { + if (ac_prefs->folder) + item = FOLDER(ac_prefs->folder)->queue; + if (!item) + item = folder_get_default_queue(); + } + break; + case F_TRASH: + if (ac_prefs->set_trash_folder && ac_prefs->trash_folder) { + item = folder_find_item_from_identifier + (ac_prefs->trash_folder); + } + if (!item) { + if (ac_prefs->folder) + item = FOLDER(ac_prefs->folder)->trash; + if (!item) + item = folder_get_default_trash(); + } + break; + default: + break; + } + + return item; +} + +void account_destroy(PrefsAccount *ac_prefs) +{ + g_return_if_fail(ac_prefs != NULL); + + folder_unref_account_all(ac_prefs); + + account_list = g_list_remove(account_list, ac_prefs); + if (cur_account == ac_prefs) + cur_account = NULL; + prefs_account_free(ac_prefs); + + if (!cur_account && account_list) { + cur_account = account_get_default(); + if (!cur_account) { + ac_prefs = (PrefsAccount *)account_list->data; + account_set_as_default(ac_prefs); + cur_account = ac_prefs; + } + } + + account_updated(); +} + +static guint account_update_lock_count = 0; + +void account_update_lock(void) +{ + account_update_lock_count++; +} + +void account_update_unlock(void) +{ + if (account_update_lock_count > 0) + account_update_lock_count--; +} + +void account_updated(void) +{ + if (account_update_lock_count) + return; + + if (address_table) { + g_hash_table_destroy(address_table); + address_table = NULL; + } + + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "account-updated"); +} diff --git a/libsylph/account.h b/libsylph/account.h new file mode 100644 index 0000000..4485114 --- /dev/null +++ b/libsylph/account.h @@ -0,0 +1,70 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2009 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __ACCOUNT_H__ +#define __ACCOUNT_H__ + +#include + +#include "prefs.h" +#include "prefs_account.h" +#include "folder.h" +#include "procmsg.h" + +typedef gint (*AccountFunc) (PrefsAccount *ac_prefs, + gpointer user_data); + +extern PrefsAccount *cur_account; + +void account_read_config_all (void); +void account_write_config_all (void); + +PrefsAccount *account_get_current_account (void); + +PrefsAccount *account_find_from_smtp_server (const gchar *address, + const gchar *smtp_server); +PrefsAccount *account_find_from_address (const gchar *address); +PrefsAccount *account_find_from_id (gint id); +PrefsAccount *account_find_from_item (FolderItem *item); +PrefsAccount *account_find_from_item_property (FolderItem *item); +PrefsAccount *account_find_from_message_file (const gchar *file); +PrefsAccount *account_find_from_msginfo (MsgInfo *msginfo); + +gboolean account_address_exist (const gchar *address); + +void account_foreach (AccountFunc func, + gpointer user_data); +GList *account_get_list (void); +void account_list_free (void); +void account_append (PrefsAccount *ac_prefs); + +void account_set_as_default (PrefsAccount *ac_prefs); +PrefsAccount *account_get_default (void); + +//void account_set_missing_folder(void); +FolderItem *account_get_special_folder(PrefsAccount *ac_prefs, + SpecialFolderItemType type); + +void account_destroy (PrefsAccount *ac_prefs); + +void account_update_lock (void); +void account_update_unlock (void); +void account_updated (void); + +#endif /* __ACCOUNT_H__ */ diff --git a/libsylph/base64.c b/libsylph/base64.c new file mode 100644 index 0000000..2e54da7 --- /dev/null +++ b/libsylph/base64.c @@ -0,0 +1,168 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include + +#include "base64.h" + +static const gchar base64char[64] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const gchar base64val[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 +}; + +#define BASE64VAL(c) (isascii((guchar)c) ? base64val[(gint)(c)] : -1) + +void base64_encode(gchar *out, const guchar *in, gint inlen) +{ + const guchar *inp = in; + gchar *outp = out; + + while (inlen >= 3) { + *outp++ = base64char[(inp[0] >> 2) & 0x3f]; + *outp++ = base64char[((inp[0] & 0x03) << 4) | + ((inp[1] >> 4) & 0x0f)]; + *outp++ = base64char[((inp[1] & 0x0f) << 2) | + ((inp[2] >> 6) & 0x03)]; + *outp++ = base64char[inp[2] & 0x3f]; + + inp += 3; + inlen -= 3; + } + + if (inlen > 0) { + *outp++ = base64char[(inp[0] >> 2) & 0x3f]; + if (inlen == 1) { + *outp++ = base64char[(inp[0] & 0x03) << 4]; + *outp++ = '='; + } else { + *outp++ = base64char[((inp[0] & 0x03) << 4) | + ((inp[1] >> 4) & 0x0f)]; + *outp++ = base64char[((inp[1] & 0x0f) << 2)]; + } + *outp++ = '='; + } + + *outp = '\0'; +} + +gint base64_decode(guchar *out, const gchar *in, gint inlen) +{ + const gchar *inp = in; + guchar *outp = out; + gchar buf[4]; + + if (inlen < 0) + inlen = G_MAXINT; + + while (inlen >= 4 && *inp != '\0') { + buf[0] = *inp++; + inlen--; + if (BASE64VAL(buf[0]) == -1) break; + + buf[1] = *inp++; + inlen--; + if (BASE64VAL(buf[1]) == -1) break; + + buf[2] = *inp++; + inlen--; + if (buf[2] != '=' && BASE64VAL(buf[2]) == -1) break; + + buf[3] = *inp++; + inlen--; + if (buf[3] != '=' && BASE64VAL(buf[3]) == -1) break; + + *outp++ = ((BASE64VAL(buf[0]) << 2) & 0xfc) | + ((BASE64VAL(buf[1]) >> 4) & 0x03); + if (buf[2] != '=') { + *outp++ = ((BASE64VAL(buf[1]) & 0x0f) << 4) | + ((BASE64VAL(buf[2]) >> 2) & 0x0f); + if (buf[3] != '=') { + *outp++ = ((BASE64VAL(buf[2]) & 0x03) << 6) | + (BASE64VAL(buf[3]) & 0x3f); + } + } + } + + return outp - out; +} + +Base64Decoder *base64_decoder_new(void) +{ + Base64Decoder *decoder; + + decoder = g_new0(Base64Decoder, 1); + return decoder; +} + +void base64_decoder_free(Base64Decoder *decoder) +{ + g_free(decoder); +} + +gint base64_decoder_decode(Base64Decoder *decoder, + const gchar *in, guchar *out) +{ + gint len, total_len = 0; + gint buf_len; + gchar buf[4]; + + g_return_val_if_fail(decoder != NULL, -1); + g_return_val_if_fail(in != NULL, -1); + g_return_val_if_fail(out != NULL, -1); + + buf_len = decoder->buf_len; + memcpy(buf, decoder->buf, sizeof(buf)); + + for (;;) { + while (buf_len < 4) { + gchar c = *in; + + in++; + if (c == '\0') break; + if (c == '\r' || c == '\n') continue; + if (c != '=' && BASE64VAL(c) == -1) + return -1; + buf[buf_len++] = c; + } + if (buf_len < 4 || buf[0] == '=' || buf[1] == '=') { + decoder->buf_len = buf_len; + memcpy(decoder->buf, buf, sizeof(buf)); + return total_len; + } + len = base64_decode(out, buf, 4); + out += len; + total_len += len; + buf_len = 0; + if (len < 3) { + decoder->buf_len = 0; + return total_len; + } + } +} diff --git a/libsylph/base64.h b/libsylph/base64.h new file mode 100644 index 0000000..d3b2c19 --- /dev/null +++ b/libsylph/base64.h @@ -0,0 +1,46 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __BASE64_H__ +#define __BASE64_H__ + +#include + +typedef struct _Base64Decoder Base64Decoder; + +struct _Base64Decoder +{ + gint buf_len; + gchar buf[4]; +}; + +void base64_encode (gchar *out, + const guchar *in, + gint inlen); +gint base64_decode (guchar *out, + const gchar *in, + gint inlen); + +Base64Decoder *base64_decoder_new (void); +void base64_decoder_free (Base64Decoder *decoder); +gint base64_decoder_decode (Base64Decoder *decoder, + const gchar *in, + guchar *out); + +#endif /* __BASE64_H__ */ diff --git a/libsylph/codeconv.c b/libsylph/codeconv.c new file mode 100644 index 0000000..49d1b3d --- /dev/null +++ b/libsylph/codeconv.c @@ -0,0 +1,2856 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2017 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include + +#if HAVE_LOCALE_H +# include +#endif + +#include + +#include "codeconv.h" +#include "prefs_common.h" +#include "unmime.h" +#include "base64.h" +#include "quoted-printable.h" +#include "utils.h" + +typedef enum +{ + JIS_ASCII, + JIS_KANJI, + JIS_HWKANA, + JIS_AUXKANJI, + JIS_UDC +} JISState; + +#define SUBST_CHAR '_' +#define ESC '\033' +#define SO 0x0e +#define SI 0x0f +#define SS2 0x8e +#define SS3 0x8f + +#define iseuckanji(c) \ + (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xfe) +#define iseuchwkana1(c) \ + (((c) & 0xff) == SS2) +#define iseuchwkana2(c) \ + (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xdf) +#define iseucaux(c) \ + (((c) & 0xff) == SS3) + +#define issjiskanji1(c) \ + ((((c) & 0xff) >= 0x81 && ((c) & 0xff) <= 0x9f) || \ + (((c) & 0xff) >= 0xe0 && ((c) & 0xff) <= 0xef)) +#define issjiskanji2(c) \ + ((((c) & 0xff) >= 0x40 && ((c) & 0xff) <= 0x7e) || \ + (((c) & 0xff) >= 0x80 && ((c) & 0xff) <= 0xfc)) +#define issjishwkana(c) \ + (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xdf) +#define issjisext(c) \ + (((c) & 0xff) >= 0xf0 && ((c) & 0xff) <= 0xfc) +#define issjisudc(c) \ + (((c) & 0xff) >= 0xf0 && ((c) & 0xff) <= 0xf9) +#define issjisibmext(c1, c2) \ + ((((c1) & 0xff) >= 0xfa && ((c1) & 0xff) <= 0xfb && \ + issjiskanji2(c2)) || \ + (((c1) & 0xff) == 0xfc && \ + ((c2) & 0xff) >= 0x40 && ((c2) & 0xff) <= 0x4b)) + +#define isjiskanji(c) \ + (((c) & 0xff) >= 0x21 && ((c) & 0xff) <= 0x7e) +#define isjishwkana(c) \ + (((c) & 0xff) >= 0x21 && ((c) & 0xff) <= 0x5f) +#define isjisudc(c) \ + (((c) & 0xff) >= 0x21 && ((c) & 0xff) <= 0x34) +#define isjisudclow(c) \ + (((c) & 0xff) >= 0x21 && ((c) & 0xff) <= 0x2a) +#define isjisudchigh(c) \ + (((c) & 0xff) >= 0x2b && ((c) & 0xff) <= 0x34) + +/* U+0080 - U+07FF */ +#define isutf8_2_1(c) \ + (((c) & 0xe0) == 0xc0) +#define isutf8_2_2(c) \ + (((c) & 0xc0) == 0x80) +/* U+0800 - U+FFFF */ +#define isutf8_3_1(c) \ + (((c) & 0xf0) == 0xe0) +#define isutf8_3_2(c) \ + (((c) & 0xc0) == 0x80) + +#define isutf8bom(s) \ + (((*(s)) & 0xff) == 0xef && ((*(s + 1)) & 0xff) == 0xbb && \ + ((*(s + 2)) & 0xff) == 0xbf) + +#define K_IN() \ + if (state != JIS_KANJI) { \ + *out++ = ESC; \ + *out++ = '$'; \ + *out++ = 'B'; \ + state = JIS_KANJI; \ + } + +#define K_OUT() \ + if (state != JIS_ASCII) { \ + *out++ = ESC; \ + *out++ = '('; \ + *out++ = 'B'; \ + state = JIS_ASCII; \ + } + +#define HW_IN() \ + if (state != JIS_HWKANA) { \ + *out++ = ESC; \ + *out++ = '('; \ + *out++ = 'I'; \ + state = JIS_HWKANA; \ + } + +#define AUX_IN() \ + if (state != JIS_AUXKANJI) { \ + *out++ = ESC; \ + *out++ = '$'; \ + *out++ = '('; \ + *out++ = 'D'; \ + state = JIS_AUXKANJI; \ + } + +#define UDC_IN() \ + if (state != JIS_UDC) { \ + *out++ = ESC; \ + *out++ = '$'; \ + *out++ = '('; \ + *out++ = '?'; \ + state = JIS_UDC; \ + } + +static ConvADType conv_ad_type = C_AD_BY_LOCALE; + +static gchar *conv_jistoeuc(const gchar *inbuf, gint *error); +static gchar *conv_jistosjis(const gchar *inbuf, gint *error); +static gchar *conv_euctojis(const gchar *inbuf, gint *error); +static gchar *conv_sjistojis(const gchar *inbuf, gint *error); +static gchar *conv_sjistoeuc(const gchar *inbuf, gint *error); + +static gchar *conv_jistoutf8(const gchar *inbuf, gint *error); +static gchar *conv_sjistoutf8(const gchar *inbuf, gint *error); +static gchar *conv_euctoutf8(const gchar *inbuf, gint *error); +static gchar *conv_anytoutf8(const gchar *inbuf, gint *error); + +static gchar *conv_utf8toeuc(const gchar *inbuf, gint *error); +static gchar *conv_utf8tojis(const gchar *inbuf, gint *error); +static gchar *conv_utf8tosjis(const gchar *inbuf, gint *error); + +/* static void conv_unreadable_eucjp(gchar *str); */ +static void conv_unreadable_8bit(gchar *str); +/* static void conv_unreadable_latin(gchar *str); */ + +static gchar *conv_jistodisp(const gchar *inbuf, gint *error); +static gchar *conv_sjistodisp(const gchar *inbuf, gint *error); +static gchar *conv_euctodisp(const gchar *inbuf, gint *error); + +static gchar *conv_anytodisp(const gchar *inbuf, gint *error); +static gchar *conv_ustodisp(const gchar *inbuf, gint *error); +static gchar *conv_noconv(const gchar *inbuf, gint *error); + +static gchar *conv_jistoeuc(const gchar *inbuf, gint *error) +{ + gchar *outbuf; + const guchar *in = (guchar *)inbuf; + guchar *out; + JISState state = JIS_ASCII; + gint error_ = 0; + + outbuf = g_malloc(strlen(inbuf) * 2 + 1); + out = (guchar *)outbuf; + + while (*in != '\0') { + if (*in == ESC) { + in++; + if (*in == '$') { + if (*(in + 1) == '@' || *(in + 1) == 'B') { + state = JIS_KANJI; + in += 2; + } else if (*(in + 1) == '(' && + *(in + 2) == 'D') { + state = JIS_AUXKANJI; + in += 3; + } else { + /* unknown escape sequence */ + error_ = -1; + state = JIS_ASCII; + } + } else if (*in == '(') { + if (*(in + 1) == 'B' || *(in + 1) == 'J') { + state = JIS_ASCII; + in += 2; + } else if (*(in + 1) == 'I') { + state = JIS_HWKANA; + in += 2; + } else { + /* unknown escape sequence */ + error_ = -1; + state = JIS_ASCII; + } + } else { + /* unknown escape sequence */ + error_ = -1; + state = JIS_ASCII; + } + } else if (*in == 0x0e) { + state = JIS_HWKANA; + in++; + } else if (*in == 0x0f) { + state = JIS_ASCII; + in++; + } else { + switch (state) { + case JIS_ASCII: + *out++ = *in++; + break; + case JIS_KANJI: + *out++ = *in++ | 0x80; + if (*in == '\0') break; + *out++ = *in++ | 0x80; + break; + case JIS_HWKANA: + *out++ = 0x8e; + *out++ = *in++ | 0x80; + break; + case JIS_AUXKANJI: + *out++ = 0x8f; + *out++ = *in++ | 0x80; + if (*in == '\0') break; + *out++ = *in++ | 0x80; + break; + default: + *out++ = *in++; + break; + } + } + } + + *out = '\0'; + + if (error) + *error = error_; + + return outbuf; +} + +static gchar *conv_jistosjis(const gchar *inbuf, gint *error) +{ + gchar *outbuf; + const guchar *in = (guchar *)inbuf; + guchar *out; + JISState state = JIS_ASCII; + gint error_ = 0; + + outbuf = g_malloc(strlen(inbuf) * 2 + 1); + out = (guchar *)outbuf; + + while (*in != '\0') { + if (*in == ESC) { + in++; + if (*in == '$') { + if (*(in + 1) == '@' || *(in + 1) == 'B') { + state = JIS_KANJI; + in += 2; + } else if (*(in + 1) == '(' && + *(in + 2) == '?') { + /* ISO-2022-JP-MS extention */ + state = JIS_UDC; + in += 3; + } else { + /* unknown escape sequence */ + error_ = -1; + state = JIS_ASCII; + } + } else if (*in == '(') { + if (*(in + 1) == 'B' || *(in + 1) == 'J') { + state = JIS_ASCII; + in += 2; + } else if (*(in + 1) == 'I') { + state = JIS_HWKANA; + in += 2; + } else { + /* unknown escape sequence */ + error_ = -1; + state = JIS_ASCII; + } + } else { + /* unknown escape sequence */ + error_ = -1; + state = JIS_ASCII; + } + } else if (*in == SO) { + state = JIS_HWKANA; + in++; + } else if (*in == SI) { + state = JIS_ASCII; + in++; + } else { + switch (state) { + case JIS_ASCII: + *out++ = *in++; + break; + case JIS_HWKANA: + *out++ = *in++ | 0x80; + break; + case JIS_KANJI: + if ((isjiskanji(*in) || + (*in >= 0x7f && *in <= 0x97)) && + isjiskanji(*(in + 1))) { + *out++ = ((*in < 0x5f) + ? (((*in - 0x21) / 2) + 0x81) + : (((*in - 0x21) / 2) + 0xc1)); + *out++ = ((*in % 2) + ? ((*(in + 1) + ((*(in + 1) < 0x60) + ? 0x1f : 0x20))) + : *(in + 1) + 0x7e); + in += 2; + } else { + error_ = -1; + *out++ = SUBST_CHAR; + in++; + if (*in != '\0') { + *out++ = SUBST_CHAR; + in++; + } + } + break; + case JIS_UDC: + if (isjisudc(*in) && isjiskanji(*(in + 1))) { + *out++ = (((*in - 0x21) / 2) + 0xf0); + *out++ = ((*in % 2) + ? ((*(in + 1) + ((*(in + 1) < 0x60) + ? 0x1f : 0x20))) + : *(in + 1) + 0x7e); + in += 2; + } else { + error_ = -1; + *out++ = SUBST_CHAR; + in++; + if (*in != '\0') { + *out++ = SUBST_CHAR; + in++; + } + } + break; + default: + *out++ = *in++; + break; + } + } + } + + *out = '\0'; + + if (error) + *error = error_; + + return outbuf; +} + +#define JIS_HWDAKUTEN 0x5e +#define JIS_HWHANDAKUTEN 0x5f + +static gint conv_jis_hantozen(guchar *outbuf, guchar jis_code, guchar sound_sym) +{ + static guint16 h2z_tbl[] = { + /* 0x20 - 0x2f */ + 0x0000, 0x2123, 0x2156, 0x2157, 0x2122, 0x2126, 0x2572, 0x2521, + 0x2523, 0x2525, 0x2527, 0x2529, 0x2563, 0x2565, 0x2567, 0x2543, + /* 0x30 - 0x3f */ + 0x213c, 0x2522, 0x2524, 0x2526, 0x2528, 0x252a, 0x252b, 0x252d, + 0x252f, 0x2531, 0x2533, 0x2535, 0x2537, 0x2539, 0x253b, 0x253d, + /* 0x40 - 0x4f */ + 0x253f, 0x2541, 0x2544, 0x2546, 0x2548, 0x254a, 0x254b, 0x254c, + 0x254d, 0x254e, 0x254f, 0x2552, 0x2555, 0x2558, 0x255b, 0x255e, + /* 0x50 - 0x5f */ + 0x255f, 0x2560, 0x2561, 0x2562, 0x2564, 0x2566, 0x2568, 0x2569, + 0x256a, 0x256b, 0x256c, 0x256d, 0x256f, 0x2573, 0x212b, 0x212c + }; + + static guint16 dakuten_tbl[] = { + /* 0x30 - 0x3f */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x252c, 0x252e, + 0x2530, 0x2532, 0x2534, 0x2536, 0x2538, 0x253a, 0x253c, 0x253e, + /* 0x40 - 0x4f */ + 0x2540, 0x2542, 0x2545, 0x2547, 0x2549, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x2550, 0x2553, 0x2556, 0x2559, 0x255c, 0x0000 + }; + + static guint16 handakuten_tbl[] = { + /* 0x4a - 0x4e */ + 0x2551, 0x2554, 0x2557, 0x255a, 0x255d + }; + + guint16 out_code; + + jis_code &= 0x7f; + sound_sym &= 0x7f; + + if (jis_code < 0x21 || jis_code > 0x5f) + return 0; + + if (sound_sym == JIS_HWDAKUTEN && + jis_code >= 0x36 && jis_code <= 0x4e) { + out_code = dakuten_tbl[jis_code - 0x30]; + if (out_code != 0) { + *outbuf = out_code >> 8; + *(outbuf + 1) = out_code & 0xff; + return 2; + } + } + + if (sound_sym == JIS_HWHANDAKUTEN && + jis_code >= 0x4a && jis_code <= 0x4e) { + out_code = handakuten_tbl[jis_code - 0x4a]; + *outbuf = out_code >> 8; + *(outbuf + 1) = out_code & 0xff; + return 2; + } + + out_code = h2z_tbl[jis_code - 0x20]; + *outbuf = out_code >> 8; + *(outbuf + 1) = out_code & 0xff; + return 1; +} + +static gchar *conv_euctojis(const gchar *inbuf, gint *error) +{ + gchar *outbuf; + const guchar *in = (guchar *)inbuf; + guchar *out; + JISState state = JIS_ASCII; + gint error_ = 0; + + outbuf = g_malloc(strlen(inbuf) * 3 + 4); + out = (guchar *)outbuf; + + while (*in != '\0') { + if (isascii(*in)) { + K_OUT(); + *out++ = *in++; + } else if (iseuckanji(*in)) { + if (iseuckanji(*(in + 1))) { + K_IN(); + *out++ = *in++ & 0x7f; + *out++ = *in++ & 0x7f; + } else { + error_ = -1; + K_OUT(); + *out++ = SUBST_CHAR; + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } else if (iseuchwkana1(*in)) { + if (iseuchwkana2(*(in + 1))) { + if (prefs_common.allow_jisx0201_kana) { + HW_IN(); + in++; + *out++ = *in++ & 0x7f; + } else { + guchar jis_ch[2]; + gint len; + + if (iseuchwkana1(*(in + 2)) && + iseuchwkana2(*(in + 3))) + len = conv_jis_hantozen + (jis_ch, + *(in + 1), *(in + 3)); + else + len = conv_jis_hantozen + (jis_ch, + *(in + 1), '\0'); + if (len == 0) + in += 2; + else { + K_IN(); + in += len * 2; + *out++ = jis_ch[0]; + *out++ = jis_ch[1]; + } + } + } else { + error_ = -1; + K_OUT(); + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } else if (iseucaux(*in)) { + in++; + if (iseuckanji(*in) && iseuckanji(*(in + 1))) { + AUX_IN(); + *out++ = *in++ & 0x7f; + *out++ = *in++ & 0x7f; + } else { + error_ = -1; + K_OUT(); + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } + } else { + error_ = -1; + K_OUT(); + *out++ = SUBST_CHAR; + in++; + } + } + + K_OUT(); + *out = '\0'; + + if (error) + *error = error_; + + return outbuf; +} + +#define sjistoidx(c1, c2) \ + (((c1) > 0x9f) \ + ? (((c1) - 0xc1) * 188 + (c2) - (((c2) > 0x7e) ? 0x41 : 0x40)) \ + : (((c1) - 0x81) * 188 + (c2) - (((c2) > 0x7e) ? 0x41 : 0x40))) +#define idxtojis1(c) (((c) / 94) + 0x21) +#define idxtojis2(c) (((c) % 94) + 0x21) + +static guint conv_idx_ibmtonec(guint idx) +{ + if (idx >= sjistoidx(0xfa, 0x5c)) + idx -= sjistoidx(0xfa, 0x5c) + - sjistoidx(0xed, 0x40); +/* else if (idx == sjistoidx(0xfa, 0x5b)) */ +/* idx = sjistoidx(0x81, 0xe6); */ +/* else if (idx == sjistoidx(0xfa, 0x5a)) */ +/* idx = sjistoidx(0x87, 0x84); */ +/* else if (idx == sjistoidx(0xfa, 0x59)) */ +/* idx = sjistoidx(0x87, 0x82); */ +/* else if (idx == sjistoidx(0xfa, 0x58)) */ +/* idx = sjistoidx(0x87, 0x8a); */ + else if (idx >= sjistoidx(0xfa, 0x55)) + idx -= sjistoidx(0xfa, 0x55) + - sjistoidx(0xee, 0xfa); +/* else if (idx == sjistoidx(0xfa, 0x54)) */ +/* idx = sjistoidx(0x81, 0xca); */ +/* else if (idx >= sjistoidx(0xfa, 0x4a)) */ +/* idx -= sjistoidx(0xfa, 0x4a) */ +/* - sjistoidx(0x87, 0x54); */ + else if (idx >= sjistoidx(0xfa, 0x40)) + idx -= sjistoidx(0xfa, 0x40) + - sjistoidx(0xee, 0xef); + return idx; +} + +static gchar *conv_sjistojis(const gchar *inbuf, gint *error) +{ + gchar *outbuf; + const guchar *in = (guchar *)inbuf; + guchar *out; + JISState state = JIS_ASCII; + gint error_ = 0; + guint idx; + + outbuf = g_malloc(strlen(inbuf) * 5 + 4); + out = (guchar *)outbuf; + + while (*in != '\0') { + if (isascii(*in)) { + K_OUT(); + *out++ = *in++; + } else if (issjiskanji1(*in)) { + if (issjiskanji2(*(in + 1))) { + K_IN(); + idx = sjistoidx(*in, *(in + 1)); + *out++ = idxtojis1(idx); + *out++ = idxtojis2(idx); + in += 2; + } else { + error_ = -1; + K_OUT(); + *out++ = SUBST_CHAR; + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } else if (issjishwkana(*in)) { + if (prefs_common.allow_jisx0201_kana) { + HW_IN(); + *out++ = *in++ & 0x7f; + } else { + guchar jis_ch[2]; + gint len; + + if (issjishwkana(*(in + 1))) + len = conv_jis_hantozen + (jis_ch, + *in, *(in + 1)); + else + len = conv_jis_hantozen + (jis_ch, + *in, '\0'); + if (len == 0) + in++; + else { + K_IN(); + in += len; + *out++ = jis_ch[0]; + *out++ = jis_ch[1]; + } + } + } else if (issjisibmext(*in, *(in + 1))) { + K_IN(); + idx = sjistoidx(*in, *(in + 1)); + idx = conv_idx_ibmtonec(idx); + *out++ = idxtojis1(idx); + *out++ = idxtojis2(idx); + in += 2; +#if 0 + } else if (issjisudc(*in)) { + UDC_IN(); + idx = sjistoidx(*in, *(in + 1)) + - sjistoidx(0xf0, 0x40); + *out++ = idxtojis1(idx); + *out++ = idxtojis2(idx); + in += 2; +#endif + } else if (issjisext(*in)) { + error_ = -1; + K_OUT(); + *out++ = SUBST_CHAR; + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } else { + error_ = -1; + K_OUT(); + *out++ = SUBST_CHAR; + in++; + } + } + + K_OUT(); + *out = '\0'; + + if (error) + *error = error_; + + return outbuf; +} + +static gchar *conv_sjistoeuc(const gchar *inbuf, gint *error) +{ + gchar *outbuf; + const guchar *in = (guchar *)inbuf; + guchar *out; + gint error_ = 0; + + outbuf = g_malloc(strlen(inbuf) * 2 + 1); + out = (guchar *)outbuf; + + while (*in != '\0') { + if (isascii(*in)) { + *out++ = *in++; + } else if (issjiskanji1(*in)) { + if (issjiskanji2(*(in + 1))) { + guchar out1 = *in; + guchar out2 = *(in + 1); + guchar row; + + row = out1 < 0xa0 ? 0x70 : 0xb0; + if (out2 < 0x9f) { + out1 = (out1 - row) * 2 - 1; + out2 -= out2 > 0x7f ? 0x20 : 0x1f; + } else { + out1 = (out1 - row) * 2; + out2 -= 0x7e; + } + + *out++ = out1 | 0x80; + *out++ = out2 | 0x80; + in += 2; + } else { + error_ = -1; + *out++ = SUBST_CHAR; + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } else if (issjishwkana(*in)) { + *out++ = SS2; + *out++ = *in++; + } else if (issjisext(*in)) { + error_ = -1; + *out++ = SUBST_CHAR; + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } else { + error_ = -1; + *out++ = SUBST_CHAR; + in++; + } + } + + *out = '\0'; + + if (error) + *error = error_; + + return outbuf; +} + +static gchar *conv_jistoutf8(const gchar *inbuf, gint *error) +{ + gchar *tmpstr, *utf8str; + gint t_error = 0, u_error = 0; + + if (strstr(inbuf, "\033$(D")) { + tmpstr = conv_jistoeuc(inbuf, &t_error); + utf8str = conv_euctoutf8(tmpstr, &u_error); + } else { + tmpstr = conv_jistosjis(inbuf, &t_error); + utf8str = conv_sjistoutf8(tmpstr, &u_error); + } + g_free(tmpstr); + + if (error) + *error = (t_error | u_error); + + return utf8str; +} + +#if USE_THREADS +#define S_LOCK_DEFINE_STATIC(name) G_LOCK_DEFINE_STATIC(name) +#define S_LOCK(name) G_LOCK(name) +#define S_UNLOCK(name) G_UNLOCK(name) +#else +#define S_LOCK_DEFINE_STATIC(name) +#define S_LOCK(name) +#define S_UNLOCK(name) +#endif + +static gchar *conv_sjistoutf8(const gchar *inbuf, gint *error) +{ + static iconv_t cd = (iconv_t)-1; + static gboolean iconv_ok = TRUE; + S_LOCK_DEFINE_STATIC(cd); + gchar *ret; + + S_LOCK(cd); + + if (cd == (iconv_t)-1) { + if (!iconv_ok) { + S_UNLOCK(cd); + if (error) + *error = -1; + return g_strdup(inbuf); + } + + cd = iconv_open(CS_UTF_8, CS_CP932); + if (cd == (iconv_t)-1) { + cd = iconv_open(CS_UTF_8, CS_SHIFT_JIS); + if (cd == (iconv_t)-1) { + g_warning("conv_sjistoutf8(): %s\n", + g_strerror(errno)); + iconv_ok = FALSE; + S_UNLOCK(cd); + if (error) + *error = -1; + return g_strdup(inbuf); + } + } + } + + ret = conv_iconv_strdup_with_cd(inbuf, cd, error); + S_UNLOCK(cd); + return ret; +} + +static gchar *conv_euctoutf8(const gchar *inbuf, gint *error) +{ + static iconv_t cd = (iconv_t)-1; + static gboolean iconv_ok = TRUE; + S_LOCK_DEFINE_STATIC(cd); + gchar *ret; + + S_LOCK(cd); + + if (cd == (iconv_t)-1) { + if (!iconv_ok) { + S_UNLOCK(cd); + if (error) + *error = -1; + return g_strdup(inbuf); + } + + cd = iconv_open(CS_UTF_8, CS_EUC_JP_MS); + if (cd == (iconv_t)-1) { + cd = iconv_open(CS_UTF_8, CS_EUC_JP); + if (cd == (iconv_t)-1) { + g_warning("conv_euctoutf8(): %s\n", + g_strerror(errno)); + iconv_ok = FALSE; + S_UNLOCK(cd); + if (error) + *error = -1; + return g_strdup(inbuf); + } + } + } + + ret = conv_iconv_strdup_with_cd(inbuf, cd, error); + S_UNLOCK(cd); + return ret; +} + +static gchar *conv_anytoutf8(const gchar *inbuf, gint *error) +{ + switch (conv_guess_ja_encoding(inbuf)) { + case C_ISO_2022_JP: + return conv_jistoutf8(inbuf, error); + case C_SHIFT_JIS: + return conv_sjistoutf8(inbuf, error); + case C_EUC_JP: + return conv_euctoutf8(inbuf, error); + case C_UTF_8: + if (error) + *error = 0; + if (isutf8bom(inbuf)) + inbuf += 3; + return g_strdup(inbuf); + default: + if (error) + *error = 0; + return g_strdup(inbuf); + } +} + +static gchar *conv_utf8tosjis(const gchar *inbuf, gint *error) +{ + static iconv_t cd = (iconv_t)-1; + static gboolean iconv_ok = TRUE; + S_LOCK_DEFINE_STATIC(cd); + gchar *ret; + + S_LOCK(cd); + + if (cd == (iconv_t)-1) { + if (!iconv_ok) { + S_UNLOCK(cd); + if (error) + *error = -1; + return g_strdup(inbuf); + } + + cd = iconv_open(CS_CP932, CS_UTF_8); + if (cd == (iconv_t)-1) { + cd = iconv_open(CS_SHIFT_JIS, CS_UTF_8); + if (cd == (iconv_t)-1) { + g_warning("conv_utf8tosjis(): %s\n", + g_strerror(errno)); + iconv_ok = FALSE; + S_UNLOCK(cd); + if (error) + *error = -1; + return g_strdup(inbuf); + } + } + } + + if (isutf8bom(inbuf)) + inbuf += 3; + ret = conv_iconv_strdup_with_cd(inbuf, cd, error); + S_UNLOCK(cd); + return ret; +} + +static gchar *conv_utf8toeuc(const gchar *inbuf, gint *error) +{ + static iconv_t cd = (iconv_t)-1; + static gboolean iconv_ok = TRUE; + S_LOCK_DEFINE_STATIC(cd); + gchar *ret; + + S_LOCK(cd); + + if (cd == (iconv_t)-1) { + if (!iconv_ok) { + S_UNLOCK(cd); + if (error) + *error = -1; + return g_strdup(inbuf); + } + + cd = iconv_open(CS_EUC_JP_MS, CS_UTF_8); + if (cd == (iconv_t)-1) { + cd = iconv_open(CS_EUC_JP, CS_UTF_8); + if (cd == (iconv_t)-1) { + g_warning("conv_utf8toeuc(): %s\n", + g_strerror(errno)); + iconv_ok = FALSE; + S_UNLOCK(cd); + if (error) + *error = -1; + return g_strdup(inbuf); + } + } + } + + if (isutf8bom(inbuf)) + inbuf += 3; + ret = conv_iconv_strdup_with_cd(inbuf, cd, error); + S_UNLOCK(cd); + return ret; +} + +static gchar *conv_utf8tojis(const gchar *inbuf, gint *error) +{ + gchar *tmpstr, *jisstr; + gint t_error = 0, j_error = 0; + +#if 1 + tmpstr = conv_utf8tosjis(inbuf, &t_error); + jisstr = conv_sjistojis(tmpstr, &j_error); +#else + tmpstr = conv_utf8toeuc(inbuf, &t_error); + jisstr = conv_euctojis(tmpstr, &j_error); +#endif + g_free(tmpstr); + + if (error) + *error = (t_error | j_error); + + return jisstr; +} + +#if 0 +static gchar valid_eucjp_tbl[][96] = { + /* 0xa2a0 - 0xa2ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0 }, + + /* 0xa3a0 - 0xa3ff */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, + + /* 0xa4a0 - 0xa4ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + /* 0xa5a0 - 0xa5ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + /* 0xa6a0 - 0xa6ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 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 }, + + /* 0xa7a0 - 0xa7ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + /* 0xa8a0 - 0xa8ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 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 } +}; + +static gboolean isprintableeuckanji(guchar c1, guchar c2) +{ + if (c1 <= 0xa0 || c1 >= 0xf5) + return FALSE; + if (c2 <= 0xa0 || c2 == 0xff) + return FALSE; + + if (c1 >= 0xa9 && c1 <= 0xaf) + return FALSE; + + if (c1 >= 0xa2 && c1 <= 0xa8) + return (gboolean)valid_eucjp_tbl[c1 - 0xa2][c2 - 0xa0]; + + if (c1 == 0xcf) { + if (c2 >= 0xd4 && c2 <= 0xfe) + return FALSE; + } else if (c1 == 0xf4) { + if (c2 >= 0xa7 && c2 <= 0xfe) + return FALSE; + } + + return TRUE; +} + +static void conv_unreadable_eucjp(gchar *str) +{ + register guchar *p = str; + + while (*p != '\0') { + if (isascii(*p)) { + /* convert CR+LF -> LF */ + if (*p == '\r' && *(p + 1) == '\n') + memmove(p, p + 1, strlen(p)); + /* printable 7 bit code */ + p++; + } else if (iseuckanji(*p)) { + if (isprintableeuckanji(*p, *(p + 1))) { + /* printable euc-jp code */ + p += 2; + } else { + /* substitute unprintable code */ + *p++ = SUBST_CHAR; + if (*p != '\0') { + if (isascii(*p)) + p++; + else + *p++ = SUBST_CHAR; + } + } + } else if (iseuchwkana1(*p)) { + if (iseuchwkana2(*(p + 1))) + /* euc-jp hankaku kana */ + p += 2; + else + *p++ = SUBST_CHAR; + } else if (iseucaux(*p)) { + if (iseuckanji(*(p + 1)) && iseuckanji(*(p + 2))) { + /* auxiliary kanji */ + p += 3; + } else + *p++ = SUBST_CHAR; + } else + /* substitute unprintable 1 byte code */ + *p++ = SUBST_CHAR; + } +} +#endif + +static void conv_unreadable_8bit(gchar *str) +{ + register gchar *p = str; + + while (*p != '\0') { + /* convert CR+LF -> LF */ + if (*p == '\r' && *(p + 1) == '\n') + memmove(p, p + 1, strlen(p)); + else if (!isascii(*(guchar *)p)) *p = SUBST_CHAR; + p++; + } +} + +#if 0 +static void conv_unreadable_latin(gchar *str) +{ + register guchar *p = str; + + while (*p != '\0') { + /* convert CR+LF -> LF */ + if (*p == '\r' && *(p + 1) == '\n') + memmove(p, p + 1, strlen(p)); + else if ((*p & 0xff) >= 0x7f && (*p & 0xff) <= 0x9f) + *p = SUBST_CHAR; + p++; + } +} +#endif + +#define NCV '\0' + +void conv_mb_alnum(gchar *str) +{ + static guchar char_tbl[] = { + /* 0xa0 - 0xaf */ + NCV, ' ', NCV, NCV, ',', '.', NCV, ':', + ';', '?', '!', NCV, NCV, NCV, NCV, NCV, + /* 0xb0 - 0xbf */ + NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV, + NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV, + /* 0xc0 - 0xcf */ + NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV, + NCV, NCV, '(', ')', NCV, NCV, '[', ']', + /* 0xd0 - 0xdf */ + '{', '}', NCV, NCV, NCV, NCV, NCV, NCV, + NCV, NCV, NCV, NCV, '+', '-', NCV, NCV, + /* 0xe0 - 0xef */ + NCV, '=', NCV, '<', '>', NCV, NCV, NCV, + NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV + }; + + register guchar *p = (guchar *)str; + register gint len; + + len = strlen(str); + + while (len > 1) { + if (*p == 0xa3) { + register guchar ch = *(p + 1); + + if (ch >= 0xb0 && ch <= 0xfa) { + /* [a-zA-Z] */ + *p = ch & 0x7f; + p++; + len--; + memmove(p, p + 1, len); + len--; + } else { + p += 2; + len -= 2; + } + } else if (*p == 0xa1) { + register guchar ch = *(p + 1); + + if (ch >= 0xa0 && ch <= 0xef && + NCV != char_tbl[ch - 0xa0]) { + *p = char_tbl[ch - 0xa0]; + p++; + len--; + memmove(p, p + 1, len); + len--; + } else { + p += 2; + len -= 2; + } + } else if (iseuckanji(*p)) { + p += 2; + len -= 2; + } else { + p++; + len--; + } + } +} + +CharSet conv_guess_ja_encoding(const gchar *str) +{ + const guchar *p = (const guchar *)str; + CharSet guessed = C_US_ASCII; + + while (*p != '\0') { + if (*p == ESC && (*(p + 1) == '$' || *(p + 1) == '(')) { + if (guessed == C_US_ASCII) + return C_ISO_2022_JP; + p += 2; + } else if (isascii(*p)) { + p++; + } else if (iseuckanji(*p) && iseuckanji(*(p + 1))) { + if (*p >= 0xfd && *p <= 0xfe) + return C_EUC_JP; + else if (guessed == C_SHIFT_JIS) { + if ((issjiskanji1(*p) && + issjiskanji2(*(p + 1))) || + issjishwkana(*p)) + guessed = C_SHIFT_JIS; + else + guessed = C_EUC_JP; + } else + guessed = C_EUC_JP; + p += 2; + } else if (issjiskanji1(*p) && issjiskanji2(*(p + 1))) { + guessed = C_SHIFT_JIS; + p += 2; + } else if (issjishwkana(*p)) { + guessed = C_SHIFT_JIS; + p++; + } else { + if (guessed == C_US_ASCII) + guessed = C_AUTO; + p++; + } + } + + if (guessed != C_US_ASCII) { + p = (const guchar *)str; + + while (*p != '\0') { + if (isascii(*p)) { + p++; + } else if (isutf8_3_1(*p) && + isutf8_3_2(*(p + 1)) && + isutf8_3_2(*(p + 2))) { + p += 3; + } else { + return guessed; + } + } + + return C_UTF_8; + } + + return guessed; +} + +static gchar *conv_jistodisp(const gchar *inbuf, gint *error) +{ + return conv_jistoutf8(inbuf, error); +} + +static gchar *conv_sjistodisp(const gchar *inbuf, gint *error) +{ + return conv_sjistoutf8(inbuf, error); +} + +static gchar *conv_euctodisp(const gchar *inbuf, gint *error) +{ + return conv_euctoutf8(inbuf, error); +} + +gchar *conv_utf8todisp(const gchar *inbuf, gint *error) +{ + if (g_utf8_validate(inbuf, -1, NULL) == TRUE) { + if (error) + *error = 0; + if (isutf8bom(inbuf)) + inbuf += 3; + return g_strdup(inbuf); + } else + return conv_ustodisp(inbuf, error); +} + +static gchar *conv_anytodisp(const gchar *inbuf, gint *error) +{ + gchar *outbuf; + + outbuf = conv_anytoutf8(inbuf, error); + if (g_utf8_validate(outbuf, -1, NULL) != TRUE) { + if (error) + *error = -1; + conv_unreadable_8bit(outbuf); + } + + return outbuf; +} + +static gchar *conv_ustodisp(const gchar *inbuf, gint *error) +{ + gchar *outbuf; + + outbuf = g_strdup(inbuf); + conv_unreadable_8bit(outbuf); + if (error) + *error = 0; + + return outbuf; +} + +gchar *conv_localetodisp(const gchar *inbuf, gint *error) +{ + gchar *str; + + str = conv_iconv_strdup(inbuf, conv_get_locale_charset_str(), + CS_INTERNAL, error); + if (!str) + str = conv_utf8todisp(inbuf, NULL); + + return str; +} + +static gchar *conv_noconv(const gchar *inbuf, gint *error) +{ + if (error) + *error = 0; + return g_strdup(inbuf); +} + +static const gchar * +conv_get_fallback_for_private_encoding(const gchar *encoding) +{ + if (encoding) { + if ((encoding[0] == 'X' || encoding[0] == 'x') && + encoding[1] == '-') { + if (!g_ascii_strcasecmp(encoding, CS_X_GBK)) + return CS_GBK; + else if (!g_ascii_strcasecmp(encoding, CS_X_SJIS)) + return CS_SHIFT_JIS; + } else if ((encoding[0] == 'K' || encoding[0] == 'k') && + (encoding[1] == 'S' || encoding[1] == 's')) { + if (!g_ascii_strcasecmp(encoding, CS_KS_C_5601_1987)) + return CS_EUC_KR; + } + } + + return encoding; +} + +CodeConverter *conv_code_converter_new(const gchar *src_encoding, + const gchar *dest_encoding) +{ + CodeConverter *conv; + + src_encoding = conv_get_fallback_for_private_encoding(src_encoding); + + conv = g_new0(CodeConverter, 1); + conv->code_conv_func = + conv_get_code_conv_func(src_encoding, dest_encoding); + conv->src_encoding = g_strdup(src_encoding); + conv->dest_encoding = g_strdup(dest_encoding); + + return conv; +} + +void conv_code_converter_destroy(CodeConverter *conv) +{ + g_free(conv->src_encoding); + g_free(conv->dest_encoding); + g_free(conv); +} + +gchar *conv_convert(CodeConverter *conv, const gchar *inbuf) +{ + if (!inbuf) + return NULL; + else if (conv->code_conv_func != conv_noconv) + return conv->code_conv_func(inbuf, NULL); + else + return conv_iconv_strdup + (inbuf, conv->src_encoding, conv->dest_encoding, NULL); +} + +gchar *conv_codeset_strdup_full(const gchar *inbuf, + const gchar *src_encoding, + const gchar *dest_encoding, + gint *error) +{ + CodeConvFunc conv_func; + + if (!inbuf) { + if (error) + *error = 0; + return NULL; + } + + src_encoding = conv_get_fallback_for_private_encoding(src_encoding); + + conv_func = conv_get_code_conv_func(src_encoding, dest_encoding); + if (conv_func != conv_noconv) + return conv_func(inbuf, error); + + return conv_iconv_strdup(inbuf, src_encoding, dest_encoding, error); +} + +CodeConvFunc conv_get_code_conv_func(const gchar *src_encoding, + const gchar *dest_encoding) +{ + CodeConvFunc code_conv = conv_noconv; + CharSet src_charset; + CharSet dest_charset; + + if (!src_encoding) + src_charset = conv_get_locale_charset(); + else + src_charset = conv_get_charset_from_str(src_encoding); + + /* auto detection mode */ + if (!src_encoding && !dest_encoding) { + if (conv_ad_type == C_AD_JAPANESE || + (conv_ad_type == C_AD_BY_LOCALE && conv_is_ja_locale())) + return conv_anytodisp; + else + return conv_noconv; + } + + dest_charset = conv_get_charset_from_str(dest_encoding); + + if (dest_charset == C_US_ASCII) + return conv_ustodisp; + + switch (src_charset) { + case C_US_ASCII: + case C_ISO_8859_1: + case C_ISO_8859_2: + case C_ISO_8859_3: + case C_ISO_8859_4: + case C_ISO_8859_5: + case C_ISO_8859_6: + case C_ISO_8859_7: + case C_ISO_8859_8: + case C_ISO_8859_9: + case C_ISO_8859_10: + case C_ISO_8859_11: + case C_ISO_8859_13: + case C_ISO_8859_14: + case C_ISO_8859_15: + case C_ISO_8859_16: + break; + case C_ISO_2022_JP: + case C_ISO_2022_JP_2: + case C_ISO_2022_JP_3: + if (dest_charset == C_AUTO) + code_conv = conv_jistodisp; + else if (dest_charset == C_EUC_JP) + code_conv = conv_jistoeuc; + else if (dest_charset == C_SHIFT_JIS || + dest_charset == C_CP932) + code_conv = conv_jistosjis; + else if (dest_charset == C_UTF_8) + code_conv = conv_jistoutf8; + break; + case C_SHIFT_JIS: + case C_CP932: + if (dest_charset == C_AUTO) + code_conv = conv_sjistodisp; + else if (dest_charset == C_ISO_2022_JP || + dest_charset == C_ISO_2022_JP_2 || + dest_charset == C_ISO_2022_JP_3) + code_conv = conv_sjistojis; + else if (dest_charset == C_EUC_JP) + code_conv = conv_sjistoeuc; + else if (dest_charset == C_UTF_8) + code_conv = conv_sjistoutf8; + break; + case C_EUC_JP: + if (dest_charset == C_AUTO) + code_conv = conv_euctodisp; + else if (dest_charset == C_ISO_2022_JP || + dest_charset == C_ISO_2022_JP_2 || + dest_charset == C_ISO_2022_JP_3) + code_conv = conv_euctojis; + else if (dest_charset == C_UTF_8) + code_conv = conv_euctoutf8; + break; + case C_UTF_8: + if (dest_charset == C_EUC_JP) + code_conv = conv_utf8toeuc; + else if (dest_charset == C_ISO_2022_JP || + dest_charset == C_ISO_2022_JP_2 || + dest_charset == C_ISO_2022_JP_3) + code_conv = conv_utf8tojis; + else if (dest_charset == C_SHIFT_JIS || + dest_charset == C_CP932) + code_conv = conv_utf8tosjis; + break; + default: + break; + } + + return code_conv; +} + +gchar *conv_iconv_strdup(const gchar *inbuf, + const gchar *src_code, const gchar *dest_code, + gint *error) +{ + iconv_t cd; + gchar *outbuf; + + if (!src_code) + src_code = conv_get_locale_charset_str(); + if (!dest_code) + dest_code = CS_INTERNAL; + + cd = iconv_open(dest_code, src_code); + if (cd == (iconv_t)-1) { + if (error) + *error = -1; + return NULL; + } + + outbuf = conv_iconv_strdup_with_cd(inbuf, cd, error); + + iconv_close(cd); + + return outbuf; +} + +gchar *conv_iconv_strdup_with_cd(const gchar *inbuf, iconv_t cd, gint *error) +{ + const gchar *inbuf_p; + gchar *outbuf; + gchar *outbuf_p; + size_t in_size; + size_t in_left; + size_t out_size; + size_t out_left; + size_t n_conv; + size_t len; + gint error_ = 0; + + if (!inbuf) { + if (error) + *error = 0; + return NULL; + } + + inbuf_p = inbuf; + in_size = strlen(inbuf); + in_left = in_size; + out_size = (in_size + 1) * 2; + outbuf = g_malloc(out_size); + outbuf_p = outbuf; + out_left = out_size; + +#define EXPAND_BUF() \ +{ \ + len = outbuf_p - outbuf; \ + out_size *= 2; \ + outbuf = g_realloc(outbuf, out_size); \ + outbuf_p = outbuf + len; \ + out_left = out_size - len; \ +} + + while ((n_conv = iconv(cd, (ICONV_CONST gchar **)&inbuf_p, &in_left, + &outbuf_p, &out_left)) == (size_t)-1) { + if (EILSEQ == errno) { + /* g_print("iconv(): at %d: %s\n", in_size - in_left, g_strerror(errno)); */ + error_ = -1; + inbuf_p++; + in_left--; + if (out_left == 0) { + EXPAND_BUF(); + } + *outbuf_p++ = SUBST_CHAR; + out_left--; + } else if (EINVAL == errno) { + error_ = -1; + break; + } else if (E2BIG == errno) { + EXPAND_BUF(); + } else { + g_warning("conv_iconv_strdup(): %s\n", + g_strerror(errno)); + error_ = -1; + break; + } + } + + while ((n_conv = iconv(cd, NULL, NULL, &outbuf_p, &out_left)) == + (size_t)-1) { + if (E2BIG == errno) { + EXPAND_BUF(); + } else { + g_warning("conv_iconv_strdup(): %s\n", + g_strerror(errno)); + error_ = -1; + break; + } + } + +#undef EXPAND_BUF + + len = outbuf_p - outbuf; + outbuf = g_realloc(outbuf, len + 1); + outbuf[len] = '\0'; + + if (error) + *error = error_; + + return outbuf; +} + +static const struct { + CharSet charset; + gchar *const name; +} charsets[] = { + {C_US_ASCII, CS_US_ASCII}, + {C_US_ASCII, CS_ANSI_X3_4_1968}, + {C_UTF_8, CS_UTF_8}, + {C_UTF_7, CS_UTF_7}, + {C_ISO_8859_1, CS_ISO_8859_1}, + {C_ISO_8859_2, CS_ISO_8859_2}, + {C_ISO_8859_3, CS_ISO_8859_3}, + {C_ISO_8859_4, CS_ISO_8859_4}, + {C_ISO_8859_5, CS_ISO_8859_5}, + {C_ISO_8859_6, CS_ISO_8859_6}, + {C_ISO_8859_7, CS_ISO_8859_7}, + {C_ISO_8859_8, CS_ISO_8859_8}, + {C_ISO_8859_9, CS_ISO_8859_9}, + {C_ISO_8859_10, CS_ISO_8859_10}, + {C_ISO_8859_11, CS_ISO_8859_11}, + {C_ISO_8859_13, CS_ISO_8859_13}, + {C_ISO_8859_14, CS_ISO_8859_14}, + {C_ISO_8859_15, CS_ISO_8859_15}, + {C_BALTIC, CS_BALTIC}, + {C_CP932, CS_CP932}, + {C_CP1250, CS_CP1250}, + {C_CP1251, CS_CP1251}, + {C_CP1252, CS_CP1252}, + {C_CP1253, CS_CP1253}, + {C_CP1254, CS_CP1254}, + {C_CP1255, CS_CP1255}, + {C_CP1256, CS_CP1256}, + {C_CP1257, CS_CP1257}, + {C_CP1258, CS_CP1258}, + {C_WINDOWS_932, CS_WINDOWS_932}, + {C_WINDOWS_1250, CS_WINDOWS_1250}, + {C_WINDOWS_1251, CS_WINDOWS_1251}, + {C_WINDOWS_1252, CS_WINDOWS_1252}, + {C_WINDOWS_1253, CS_WINDOWS_1253}, + {C_WINDOWS_1254, CS_WINDOWS_1254}, + {C_WINDOWS_1255, CS_WINDOWS_1255}, + {C_WINDOWS_1256, CS_WINDOWS_1256}, + {C_WINDOWS_1257, CS_WINDOWS_1257}, + {C_WINDOWS_1258, CS_WINDOWS_1258}, + {C_KOI8_R, CS_KOI8_R}, + {C_KOI8_T, CS_KOI8_T}, + {C_KOI8_U, CS_KOI8_U}, + {C_ISO_2022_JP, CS_ISO_2022_JP}, + {C_ISO_2022_JP_2, CS_ISO_2022_JP_2}, + {C_ISO_2022_JP_3, CS_ISO_2022_JP_3}, + {C_EUC_JP, CS_EUC_JP}, + {C_EUC_JP, CS_EUCJP}, + {C_EUC_JP_MS, CS_EUC_JP_MS}, + {C_SHIFT_JIS, CS_SHIFT_JIS}, + {C_SHIFT_JIS, CS_SHIFT__JIS}, + {C_SHIFT_JIS, CS_SJIS}, + {C_ISO_2022_KR, CS_ISO_2022_KR}, + {C_EUC_KR, CS_EUC_KR}, + {C_ISO_2022_CN, CS_ISO_2022_CN}, + {C_EUC_CN, CS_EUC_CN}, + {C_GB2312, CS_GB2312}, + {C_GBK, CS_GBK}, + {C_EUC_TW, CS_EUC_TW}, + {C_BIG5, CS_BIG5}, + {C_BIG5_HKSCS, CS_BIG5_HKSCS}, + {C_TIS_620, CS_TIS_620}, + {C_WINDOWS_874, CS_WINDOWS_874}, + {C_GEORGIAN_PS, CS_GEORGIAN_PS}, + {C_TCVN5712_1, CS_TCVN5712_1}, + {C_ISO_8859_16, CS_ISO_8859_16}, + {C_UTF_16, CS_UTF_16}, + {C_UTF_16BE, CS_UTF_16BE}, + {C_UTF_16LE, CS_UTF_16LE}, +}; + +static const struct { + gchar *const locale; + CharSet charset; + CharSet out_charset; +} locale_table[] = { + {"ja_JP.eucJP" , C_EUC_JP , C_ISO_2022_JP}, + {"ja_JP.EUC-JP" , C_EUC_JP , C_ISO_2022_JP}, + {"ja_JP.EUC" , C_EUC_JP , C_ISO_2022_JP}, + {"ja_JP.ujis" , C_EUC_JP , C_ISO_2022_JP}, + {"ja_JP.SJIS" , C_SHIFT_JIS , C_ISO_2022_JP}, + {"ja_JP.JIS" , C_ISO_2022_JP , C_ISO_2022_JP}, +#ifdef G_OS_WIN32 + {"ja_JP" , C_CP932 , C_ISO_2022_JP}, +#elif defined(__APPLE__) + {"ja_JP" , C_UTF_8 , C_ISO_2022_JP}, +#else + {"ja_JP" , C_EUC_JP , C_ISO_2022_JP}, +#endif + {"ko_KR.EUC-KR" , C_EUC_KR , C_EUC_KR}, + {"ko_KR" , C_EUC_KR , C_EUC_KR}, + {"zh_CN.GB2312" , C_GB2312 , C_GB2312}, + {"zh_CN.GBK" , C_GBK , C_GBK}, + {"zh_CN" , C_GB2312 , C_GB2312}, + {"zh_HK" , C_BIG5_HKSCS , C_BIG5_HKSCS}, + {"zh_TW.eucTW" , C_EUC_TW , C_BIG5}, + {"zh_TW.EUC-TW" , C_EUC_TW , C_BIG5}, + {"zh_TW.Big5" , C_BIG5 , C_BIG5}, + {"zh_TW" , C_BIG5 , C_BIG5}, + + {"ru_RU.KOI8-R" , C_KOI8_R , C_KOI8_R}, + {"ru_RU.KOI8R" , C_KOI8_R , C_KOI8_R}, + {"ru_RU.CP1251" , C_WINDOWS_1251, C_KOI8_R}, + {"ru_RU" , C_ISO_8859_5 , C_KOI8_R}, + {"tg_TJ" , C_KOI8_T , C_KOI8_T}, + {"ru_UA" , C_KOI8_U , C_KOI8_U}, + {"uk_UA.CP1251" , C_WINDOWS_1251, C_KOI8_U}, + {"uk_UA" , C_KOI8_U , C_KOI8_U}, + + {"be_BY" , C_WINDOWS_1251, C_WINDOWS_1251}, + {"bg_BG" , C_WINDOWS_1251, C_WINDOWS_1251}, + + {"yi_US" , C_WINDOWS_1255, C_WINDOWS_1255}, + + {"af_ZA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"br_FR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"ca_ES" , C_ISO_8859_1 , C_ISO_8859_1}, + {"da_DK" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_AT" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_BE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_CH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_DE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_LU" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_AU" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_BW" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_CA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_DK" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_GB" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_HK" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_IE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_NZ" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_PH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_SG" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_US" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_ZA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_ZW" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_AR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_BO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_CL" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_CO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_CR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_DO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_EC" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_ES" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_GT" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_HN" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_MX" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_NI" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_PA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_PE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_PR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_PY" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_SV" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_US" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_UY" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_VE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"et_EE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"eu_ES" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fi_FI" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fo_FO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_BE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_CA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_CH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_FR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_LU" , C_ISO_8859_1 , C_ISO_8859_1}, + {"ga_IE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"gl_ES" , C_ISO_8859_1 , C_ISO_8859_1}, + {"gv_GB" , C_ISO_8859_1 , C_ISO_8859_1}, + {"id_ID" , C_ISO_8859_1 , C_ISO_8859_1}, + {"is_IS" , C_ISO_8859_1 , C_ISO_8859_1}, + {"it_CH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"it_IT" , C_ISO_8859_1 , C_ISO_8859_1}, + {"kl_GL" , C_ISO_8859_1 , C_ISO_8859_1}, + {"kw_GB" , C_ISO_8859_1 , C_ISO_8859_1}, + {"ms_MY" , C_ISO_8859_1 , C_ISO_8859_1}, + {"nl_BE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"nl_NL" , C_ISO_8859_1 , C_ISO_8859_1}, + {"nn_NO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"no_NO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"oc_FR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"pt_BR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"pt_PT" , C_ISO_8859_1 , C_ISO_8859_1}, + {"sq_AL" , C_ISO_8859_1 , C_ISO_8859_1}, + {"sv_FI" , C_ISO_8859_1 , C_ISO_8859_1}, + {"sv_SE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"tl_PH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"uz_UZ" , C_ISO_8859_1 , C_ISO_8859_1}, + {"wa_BE" , C_ISO_8859_1 , C_ISO_8859_1}, + + {"bs_BA" , C_ISO_8859_2 , C_ISO_8859_2}, + {"cs_CZ" , C_ISO_8859_2 , C_ISO_8859_2}, + {"hr_HR" , C_ISO_8859_2 , C_ISO_8859_2}, + {"hu_HU" , C_ISO_8859_2 , C_ISO_8859_2}, + {"pl_PL" , C_ISO_8859_2 , C_ISO_8859_2}, + {"ro_RO" , C_ISO_8859_2 , C_ISO_8859_2}, + {"sk_SK" , C_ISO_8859_2 , C_ISO_8859_2}, + {"sl_SI" , C_ISO_8859_2 , C_ISO_8859_2}, + + {"sr_YU@cyrillic" , C_ISO_8859_5 , C_ISO_8859_5}, + {"sr_YU" , C_ISO_8859_2 , C_ISO_8859_2}, + + {"mt_MT" , C_ISO_8859_3 , C_ISO_8859_3}, + + {"lt_LT.iso88594" , C_ISO_8859_4 , C_ISO_8859_4}, + {"lt_LT.ISO8859-4" , C_ISO_8859_4 , C_ISO_8859_4}, + {"lt_LT.ISO_8859-4" , C_ISO_8859_4 , C_ISO_8859_4}, + {"lt_LT" , C_ISO_8859_13 , C_ISO_8859_13}, + + {"mk_MK" , C_ISO_8859_5 , C_ISO_8859_5}, + + {"ar_AE" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_BH" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_DZ" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_EG" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_IQ" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_JO" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_KW" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_LB" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_LY" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_MA" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_OM" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_QA" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_SA" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_SD" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_SY" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_TN" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_YE" , C_ISO_8859_6 , C_ISO_8859_6}, + + {"el_GR" , C_ISO_8859_7 , C_ISO_8859_7}, + {"he_IL" , C_ISO_8859_8 , C_ISO_8859_8}, + {"iw_IL" , C_ISO_8859_8 , C_ISO_8859_8}, + {"tr_TR" , C_ISO_8859_9 , C_ISO_8859_9}, + + {"lv_LV" , C_ISO_8859_13 , C_ISO_8859_13}, + {"mi_NZ" , C_ISO_8859_13 , C_ISO_8859_13}, + + {"cy_GB" , C_ISO_8859_14 , C_ISO_8859_14}, + + {"ar_IN" , C_UTF_8 , C_UTF_8}, + {"en_IN" , C_UTF_8 , C_UTF_8}, + {"se_NO" , C_UTF_8 , C_UTF_8}, + {"ta_IN" , C_UTF_8 , C_UTF_8}, + {"te_IN" , C_UTF_8 , C_UTF_8}, + {"ur_PK" , C_UTF_8 , C_UTF_8}, + + {"th_TH" , C_TIS_620 , C_TIS_620}, + /* {"th_TH" , C_WINDOWS_874}, */ + /* {"th_TH" , C_ISO_8859_11}, */ + + {"ka_GE" , C_GEORGIAN_PS , C_GEORGIAN_PS}, + {"vi_VN.TCVN" , C_TCVN5712_1 , C_TCVN5712_1}, + + {"C" , C_US_ASCII , C_US_ASCII}, + {"POSIX" , C_US_ASCII , C_US_ASCII}, + {"ANSI_X3.4-1968" , C_US_ASCII , C_US_ASCII}, +}; + +static GHashTable *conv_get_charset_to_str_table(void) +{ + static GHashTable *table; + gint i; + S_LOCK_DEFINE_STATIC(table); + + S_LOCK(table); + + if (table) { + S_UNLOCK(table); + return table; + } + + table = g_hash_table_new(NULL, g_direct_equal); + + for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) { + if (g_hash_table_lookup(table, GUINT_TO_POINTER(charsets[i].charset)) + == NULL) { + g_hash_table_insert + (table, GUINT_TO_POINTER(charsets[i].charset), + charsets[i].name); + } + } + + S_UNLOCK(table); + return table; +} + +static GHashTable *conv_get_charset_from_str_table(void) +{ + static GHashTable *table; + S_LOCK_DEFINE_STATIC(table); + + gint i; + + S_LOCK(table); + + if (table) { + S_UNLOCK(table); + return table; + } + + table = g_hash_table_new(str_case_hash, str_case_equal); + + for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) { + g_hash_table_insert(table, charsets[i].name, + GUINT_TO_POINTER(charsets[i].charset)); + } + + S_UNLOCK(table); + return table; +} + +const gchar *conv_get_charset_str(CharSet charset) +{ + GHashTable *table; + + table = conv_get_charset_to_str_table(); + return g_hash_table_lookup(table, GUINT_TO_POINTER(charset)); +} + +CharSet conv_get_charset_from_str(const gchar *charset) +{ + GHashTable *table; + + if (!charset) return C_AUTO; + + table = conv_get_charset_from_str_table(); + return GPOINTER_TO_UINT(g_hash_table_lookup(table, charset)); +} + +CharSet conv_get_locale_charset(void) +{ + static CharSet cur_charset = -1; + const gchar *cur_locale; + const gchar *p; +#if !defined(G_OS_WIN32) && !defined(__APPLE__) + gint i; +#endif + S_LOCK_DEFINE_STATIC(cur_charset); + + S_LOCK(cur_charset); + + if (cur_charset != -1) { + S_UNLOCK(cur_charset); + return cur_charset; + } + + cur_locale = conv_get_current_locale(); + if (!cur_locale) { + cur_charset = C_US_ASCII; + S_UNLOCK(cur_charset); + return cur_charset; + } + + if (strcasestr(cur_locale, "UTF-8") || strcasestr(cur_locale, "utf8")) { + cur_charset = C_UTF_8; + S_UNLOCK(cur_charset); + return cur_charset; + } + + if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') { + cur_charset = C_ISO_8859_15; + S_UNLOCK(cur_charset); + return cur_charset; + } + +#if defined(G_OS_WIN32) || defined(__APPLE__) + cur_charset = conv_get_charset_from_str(conv_get_locale_charset_str()); + + S_UNLOCK(cur_charset); + return cur_charset; +#else + for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) { + const gchar *p; + + /* "ja_JP.EUC" matches with "ja_JP.eucJP", "ja_JP.EUC" and + "ja_JP". "ja_JP" matches with "ja_JP.xxxx" and "ja" */ + if (!g_ascii_strncasecmp(cur_locale, locale_table[i].locale, + strlen(locale_table[i].locale))) { + cur_charset = locale_table[i].charset; + S_UNLOCK(cur_charset); + return cur_charset; + } else if ((p = strchr(locale_table[i].locale, '_')) && + !strchr(p + 1, '.')) { + if (strlen(cur_locale) == 2 && + !g_ascii_strncasecmp(cur_locale, + locale_table[i].locale, 2)) { + cur_charset = locale_table[i].charset; + S_UNLOCK(cur_charset); + return cur_charset; + } + } + } + + cur_charset = C_AUTO; + S_UNLOCK(cur_charset); + return cur_charset; +#endif +} + +const gchar *conv_get_locale_charset_str(void) +{ + static const gchar *codeset = NULL; + S_LOCK_DEFINE_STATIC(codeset); + + S_LOCK(codeset); + + if (!codeset) { +#if defined(G_OS_WIN32) || defined(__APPLE__) + g_get_charset(&codeset); + if (!strcmp(codeset, CS_US_ASCII) || + !strcmp(codeset, CS_ANSI_X3_4_1968)) + codeset = CS_INTERNAL; +#else + codeset = conv_get_charset_str(conv_get_locale_charset()); +#endif + } + + if (codeset) { + S_UNLOCK(codeset); + return codeset; + } + + S_UNLOCK(codeset); + return CS_INTERNAL; +} + +CharSet conv_get_internal_charset(void) +{ + return C_INTERNAL; +} + +const gchar *conv_get_internal_charset_str(void) +{ + return CS_INTERNAL; +} + +CharSet conv_get_outgoing_charset(void) +{ + static CharSet out_charset = -1; + const gchar *cur_locale; + const gchar *p; + gint i; + S_LOCK_DEFINE_STATIC(out_charset); + + S_LOCK(out_charset); + + if (out_charset != -1) { + S_UNLOCK(out_charset); + return out_charset; + } + + cur_locale = conv_get_current_locale(); + if (!cur_locale) { + out_charset = C_AUTO; + S_UNLOCK(out_charset); + return out_charset; + } + + if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') { + out_charset = C_ISO_8859_15; + S_UNLOCK(out_charset); + return out_charset; + } + + for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) { + const gchar *p; + + if (!g_ascii_strncasecmp(cur_locale, locale_table[i].locale, + strlen(locale_table[i].locale))) { + out_charset = locale_table[i].out_charset; + break; + } else if ((p = strchr(locale_table[i].locale, '_')) && + !strchr(p + 1, '.')) { + if (strlen(cur_locale) == 2 && + !g_ascii_strncasecmp(cur_locale, + locale_table[i].locale, 2)) { + out_charset = locale_table[i].out_charset; + break; + } + } + } + + S_UNLOCK(out_charset); + return out_charset; +} + +const gchar *conv_get_outgoing_charset_str(void) +{ + CharSet out_charset; + const gchar *str; + + out_charset = conv_get_outgoing_charset(); + str = conv_get_charset_str(out_charset); + + return str ? str : CS_UTF_8; +} + +gboolean conv_is_multibyte_encoding(CharSet encoding) +{ + switch (encoding) { + case C_EUC_JP: + case C_EUC_JP_MS: + case C_EUC_KR: + case C_EUC_TW: + case C_EUC_CN: + case C_ISO_2022_JP: + case C_ISO_2022_JP_2: + case C_ISO_2022_JP_3: + case C_ISO_2022_KR: + case C_ISO_2022_CN: + case C_SHIFT_JIS: + case C_CP932: + case C_GB2312: + case C_GBK: + case C_BIG5: + case C_UTF_8: + case C_UTF_7: + return TRUE; + default: + return FALSE; + } +} + +const gchar *conv_get_current_locale(void) +{ + static const gchar *cur_locale; + S_LOCK_DEFINE_STATIC(cur_locale); + + S_LOCK(cur_locale); + + if (!cur_locale) { +#ifdef G_OS_WIN32 + cur_locale = g_win32_getlocale(); +#else + cur_locale = g_getenv("LC_ALL"); + if (!cur_locale || *cur_locale == '\0') + cur_locale = g_getenv("LC_CTYPE"); + if (!cur_locale || *cur_locale == '\0') + cur_locale = g_getenv("LANG"); +#ifdef HAVE_LOCALE_H + if (!cur_locale || *cur_locale == '\0') + cur_locale = setlocale(LC_CTYPE, NULL); +#endif /* HAVE_LOCALE_H */ +#endif /* G_OS_WIN32 */ + + debug_print("current locale: %s\n", + cur_locale ? cur_locale : "(none)"); + } + + S_UNLOCK(cur_locale); + return cur_locale; +} + +gboolean conv_is_ja_locale(void) +{ + static gint is_ja_locale = -1; + const gchar *cur_locale; + S_LOCK_DEFINE_STATIC(is_ja_locale); + + S_LOCK(is_ja_locale); + + if (is_ja_locale != -1) { + S_UNLOCK(is_ja_locale); + return is_ja_locale != 0; + } + + is_ja_locale = 0; + cur_locale = conv_get_current_locale(); + if (cur_locale) { + if (g_ascii_strncasecmp(cur_locale, "ja", 2) == 0) + is_ja_locale = 1; + } + + S_UNLOCK(is_ja_locale); + return is_ja_locale != 0; +} + +void conv_set_autodetect_type(ConvADType type) +{ + conv_ad_type = type; +} + +ConvADType conv_get_autodetect_type(void) +{ + return conv_ad_type; +} + +gchar *conv_unmime_header(const gchar *str, const gchar *default_encoding) +{ + gchar *buf; + gchar *decoded_str; + + if (is_ascii_str(str)) + return unmime_header(str); + + if (default_encoding) { + buf = conv_codeset_strdup + (str, default_encoding, CS_INTERNAL); + if (buf) { + decoded_str = unmime_header(buf); + g_free(buf); + return decoded_str; + } + } + + if (conv_ad_type == C_AD_JAPANESE || + (conv_ad_type == C_AD_BY_LOCALE && conv_is_ja_locale())) + buf = conv_anytodisp(str, NULL); + else + buf = conv_localetodisp(str, NULL); + + decoded_str = unmime_header(buf); + g_free(buf); + + return decoded_str; +} + +#define MAX_LINELEN 76 +#define MAX_HARD_LINELEN 996 +#define MIMESEP_BEGIN "=?" +#define MIMESEP_END "?=" + +#define B64LEN(len) ((len) / 3 * 4 + ((len) % 3 ? 4 : 0)) + +#define LBREAK_IF_REQUIRED(cond, is_plain_text) \ +{ \ + if (len - (destp - dest) < MAX_LINELEN + 2) { \ + *destp = '\0'; \ + return; \ + } \ + \ + if ((cond) && *srcp) { \ + if (destp > dest && left < MAX_LINELEN - 1) { \ + if (g_ascii_isspace(*(destp - 1))) \ + destp--; \ + else if (is_plain_text && \ + g_ascii_isspace(*srcp)) \ + srcp++; \ + if (*srcp) { \ + *destp++ = '\n'; \ + *destp++ = ' '; \ + left = MAX_LINELEN - 1; \ + } \ + } \ + } \ +} + +void conv_encode_header(gchar *dest, gint len, const gchar *src, + gint header_len, gboolean addr_field, + const gchar *out_encoding) +{ + const gchar *src_encoding; + gint mimestr_len; + gchar *mimesep_enc; + gint left; + const gchar *srcp = src; + gchar *destp = dest; + gboolean use_base64; + + g_return_if_fail(g_utf8_validate(src, -1, NULL) == TRUE); + + src_encoding = CS_INTERNAL; + if (!out_encoding) + out_encoding = conv_get_outgoing_charset_str(); + if (!strcmp(out_encoding, CS_US_ASCII)) + out_encoding = CS_ISO_8859_1; + + if (!g_ascii_strncasecmp(out_encoding, "ISO-8859-", 9) || + !g_ascii_strncasecmp(out_encoding, "KOI8-", 5) || + !g_ascii_strncasecmp(out_encoding, "Windows-", 8)) { + use_base64 = FALSE; + mimesep_enc = "?Q?"; + } else { + use_base64 = TRUE; + mimesep_enc = "?B?"; + } + + mimestr_len = strlen(MIMESEP_BEGIN) + strlen(mimesep_enc) + + strlen(MIMESEP_END); + + left = MAX_LINELEN - header_len; + + while (*srcp) { + gboolean in_quote = FALSE; + + LBREAK_IF_REQUIRED(left <= 0, TRUE); + + while (g_ascii_isspace(*srcp)) { + *destp++ = *srcp++; + left--; + LBREAK_IF_REQUIRED(left <= 0, TRUE); + } + + /* output as it is if the next word is ASCII string */ + if (!is_next_nonascii(srcp)) { + gint word_len; + + word_len = get_next_word_len(srcp); + LBREAK_IF_REQUIRED(left < word_len, TRUE); + while (word_len > 0) { + LBREAK_IF_REQUIRED(left + (MAX_HARD_LINELEN - MAX_LINELEN) <= 0, TRUE) + *destp++ = *srcp++; + left--; + word_len--; + } + + continue; + } + + /* don't include parentheses in encoded strings */ + if (addr_field && (*srcp == '(' || *srcp == ')')) { + LBREAK_IF_REQUIRED(left < 2, FALSE); + *destp++ = *srcp++; + left--; + } + + while (1) { + gint mb_len = 0; + gint cur_len = 0; + gchar *part_str; + gchar *out_str; + gchar *enc_str; + const gchar *p = srcp; + const gchar *block_encoding = out_encoding; + gint out_str_len; + gint out_enc_str_len; + gint mime_block_len; + gint error = 0; + gboolean cont = FALSE; + + while (*p != '\0') { + if (*p == '"') + in_quote ^= TRUE; + else if (!in_quote) { + if (g_ascii_isspace(*p) && + !is_next_nonascii(p + 1)) + break; + /* don't include parentheses in encoded + strings */ + if (addr_field && + (*p == '(' || *p == ')')) + break; + } + + mb_len = g_utf8_skip[*(guchar *)p]; + + part_str = g_strndup(srcp, cur_len + mb_len); + out_str = conv_codeset_strdup_full + (part_str, src_encoding, block_encoding, + &error); + if (!out_str || error != 0) { + g_warning("conv_encode_header(): code conversion failed. Keeping UTF-8.\n"); + out_str = g_strdup(part_str); + block_encoding = CS_UTF_8; + } + out_str_len = strlen(out_str); + + if (use_base64) + out_enc_str_len = B64LEN(out_str_len); + else + out_enc_str_len = + qp_get_q_encoding_len + ((guchar *)out_str); + + g_free(out_str); + g_free(part_str); + + if (mimestr_len + strlen(block_encoding) + out_enc_str_len <= left) { + cur_len += mb_len; + p += mb_len; + } else if (cur_len == 0) { + LBREAK_IF_REQUIRED(1, FALSE); + if (*p == '"') + in_quote ^= TRUE; + continue; + } else { + cont = TRUE; + if (*p == '"') + in_quote ^= TRUE; + break; + } + } + + if (cur_len > 0) { + error = 0; + part_str = g_strndup(srcp, cur_len); + out_str = conv_codeset_strdup_full + (part_str, src_encoding, block_encoding, + &error); + if (!out_str || error != 0) { + g_warning("conv_encode_header(): code conversion failed\n"); + out_str = g_strdup(part_str); + block_encoding = CS_UTF_8; + } + out_str_len = strlen(out_str); + + if (use_base64) + out_enc_str_len = B64LEN(out_str_len); + else + out_enc_str_len = + qp_get_q_encoding_len + ((guchar *)out_str); + + enc_str = g_malloc(out_enc_str_len + 1); + if (use_base64) + base64_encode(enc_str, + (guchar *)out_str, + out_str_len); + else + qp_q_encode(enc_str, (guchar *)out_str); + + /* output MIME-encoded string block */ + mime_block_len = mimestr_len + + strlen(block_encoding) + + strlen(enc_str); + g_snprintf(destp, mime_block_len + 1, + MIMESEP_BEGIN "%s%s%s" MIMESEP_END, + block_encoding, mimesep_enc, + enc_str); + destp += mime_block_len; + srcp += cur_len; + + left -= mime_block_len; + + g_free(enc_str); + g_free(out_str); + g_free(part_str); + } + + LBREAK_IF_REQUIRED(cont, FALSE); + + if (cur_len == 0) + break; + } + } + + *destp = '\0'; +} + +#undef LBREAK_IF_REQUIRED + +#define INT_TO_HEX_UPPER(outp, val) \ +{ \ + if ((val) < 10) \ + *outp = '0' + (val); \ + else \ + *outp = 'A' + (val) - 10; \ +} + +#define IS_ESCAPE_CHAR(c) \ + (c < 0x20 || c > 0x7f || \ + strchr("\t \r\n*'%!#$&~`,{}|()<>@,;:\\\"/[]?=", c)) + +static gchar *encode_rfc2231_filename(const gchar *str) +{ + const gchar *p; + gchar *out; + gchar *outp; + + outp = out = g_malloc(strlen(str) * 3 + 1); + + for (p = str; *p != '\0'; ++p) { + guchar ch = *(guchar *)p; + + if (IS_ESCAPE_CHAR(ch)) { + *outp++ = '%'; + INT_TO_HEX_UPPER(outp, ch >> 4); + ++outp; + INT_TO_HEX_UPPER(outp, ch & 0x0f); + ++outp; + } else + *outp++ = ch; + } + + *outp = '\0'; + return out; +} + +gchar *conv_encode_filename(const gchar *src, const gchar *param_name, + const gchar *out_encoding) +{ + gint name_len, max_linelen; + gchar *out_str, *enc_str; + gchar cur_param[80]; + GString *string; + gint count = 0; + gint cur_left_len; + gchar *p; + + g_return_val_if_fail(src != NULL, NULL); + g_return_val_if_fail(param_name != NULL, NULL); + + if (is_ascii_str(src)) + return g_strdup_printf(" %s=\"%s\"", param_name, src); + + name_len = strlen(param_name); + max_linelen = MAX_LINELEN - name_len - 3; + + if (!out_encoding) + out_encoding = conv_get_outgoing_charset_str(); + if (!strcmp(out_encoding, CS_US_ASCII)) + out_encoding = CS_ISO_8859_1; + + out_str = conv_codeset_strdup(src, CS_INTERNAL, out_encoding); + if (!out_str) + return NULL; + enc_str = encode_rfc2231_filename(out_str); + g_free(out_str); + + if (strlen(enc_str) <= max_linelen) { + gchar *ret; + ret = g_strdup_printf(" %s*=%s''%s", + param_name, out_encoding, enc_str); + g_free(enc_str); + return ret; + } + + string = g_string_new(NULL); + g_string_printf(string, " %s*0*=%s''", param_name, out_encoding); + cur_left_len = MAX_LINELEN - string->len; + + p = enc_str; + + while (*p != '\0') { + if ((*p == '%' && cur_left_len < 4) || + (*p != '%' && cur_left_len < 2)) { + gint len; + + g_string_append(string, ";\n"); + ++count; + len = g_snprintf(cur_param, sizeof(cur_param), + " %s*%d*=", param_name, count); + g_string_append(string, cur_param); + cur_left_len = MAX_LINELEN - len; + } + + if (*p == '%') { + g_string_append_len(string, p, 3); + p += 3; + cur_left_len -= 3; + } else { + g_string_append_c(string, *p); + ++p; + --cur_left_len; + } + } + + g_free(enc_str); + + return g_string_free(string, FALSE); +} + +static gint conv_copy_file_with_gconvert(const gchar *src, const gchar *dest, + const gchar *encoding) +{ + gchar *src_s = NULL; + gsize len = 0, dlen = 0; + gchar *dest_s; + GError *error = NULL; + + g_return_val_if_fail(src != NULL, -1); + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(encoding != NULL, -1); + + if (g_file_get_contents(src, &src_s, &len, &error) == FALSE) { + g_warning("conv_copy_utf16_file(): %s: %s", src, error->message); + g_error_free(error); + return -1; + } + + dest_s = g_convert(src_s, len, CS_UTF_8, encoding, NULL, &dlen, &error); + if (!dest_s) { + g_warning("conv_copy_utf16_file(): %s: %s", src, error->message); + g_error_free(error); + g_free(src_s); + return -1; + } + + if (g_file_set_contents(dest, dest_s, dlen, &error) == FALSE) { + g_warning("conv_copy_utf16_file(): %s: %s", dest, error->message); + g_error_free(error); + g_free(dest_s); + g_free(src_s); + return -1; + } + + g_free(dest_s); + g_free(src_s); + return 0; +} + +gint conv_copy_file(const gchar *src, const gchar *dest, const gchar *encoding) +{ + FILE *src_fp, *dest_fp; + gchar buf[BUFFSIZE]; + CodeConverter *conv; + gboolean err = FALSE; + CharSet charset; + + charset = conv_get_charset_from_str(encoding); + if (charset == C_UTF_16 || charset == C_UTF_16BE || charset == C_UTF_16LE) { + return conv_copy_file_with_gconvert(src, dest, encoding); + } + + if ((src_fp = g_fopen(src, "rb")) == NULL) { + FILE_OP_ERROR(src, "fopen"); + return -1; + } + if ((dest_fp = g_fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + fclose(src_fp); + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning("can't change file mode\n"); + } + + conv = conv_code_converter_new(encoding, NULL); + + while (fgets(buf, sizeof(buf), src_fp) != NULL) { + gchar *outbuf; + + outbuf = conv_convert(conv, buf); + if (outbuf) { + fputs(outbuf, dest_fp); + g_free(outbuf); + } else + fputs(buf, dest_fp); + } + + conv_code_converter_destroy(conv); + + if (ferror(src_fp)) { + FILE_OP_ERROR(src, "fgets"); + err = TRUE; + } + fclose(src_fp); + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + err = TRUE; + } + if (err) { + g_unlink(dest); + return -1; + } + + return 0; +} + +gint conv_copy_dir(const gchar *src, const gchar *dest, const gchar *encoding) +{ + GDir *dir; + const gchar *dir_name; + gchar *src_file; + gchar *dest_file; + + if ((dir = g_dir_open(src, 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", src); + return -1; + } + + if (make_dir_hier(dest) < 0) { + g_dir_close(dir); + return -1; + } + + while ((dir_name = g_dir_read_name(dir)) != NULL) { + src_file = g_strconcat(src, G_DIR_SEPARATOR_S, dir_name, NULL); + dest_file = g_strconcat(dest, G_DIR_SEPARATOR_S, dir_name, + NULL); + if (is_file_exist(src_file)) + conv_copy_file(src_file, dest_file, encoding); + g_free(dest_file); + g_free(src_file); + } + + g_dir_close(dir); + + return 0; +} + +CharSet conv_check_file_encoding(const gchar *file) +{ + FILE *fp; + gchar buf[BUFFSIZE]; + CharSet enc; + const gchar *enc_str; + gboolean is_locale = TRUE, is_utf8 = TRUE; + size_t size; + + g_return_val_if_fail(file != NULL, C_AUTO); + + enc = conv_get_locale_charset(); + enc_str = conv_get_locale_charset_str(); + if (enc == C_UTF_8) + is_locale = FALSE; + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return C_AUTO; + } + + /* UTF-16 check */ + if ((size = fread(buf, 2, BUFFSIZE / 2, fp)) > 0) { + CharSet guess_enc = C_AUTO; + + debug_print("conv_check_file_encoding: check first %d bytes of file %s\n", size * 2, file); + + /* BOM check */ + if ((buf[0] & 0xff) == 0xfe && (buf[1] & 0xff) == 0xff) { + debug_print("conv_check_file_encoding: UTF-16 BOM (BE) found\n"); + guess_enc = C_UTF_16; /* UTF-16BE */ + } else if ((buf[0] & 0xff) == 0xff && (buf[1] & 0xff) == 0xfe) { + debug_print("conv_check_file_encoding: UTF-16 BOM (LE) found\n"); + guess_enc = C_UTF_16; /* UTF-16LE */ + } + if (guess_enc != C_AUTO) { + fclose(fp); + return guess_enc; + } + + /* search UTF-16 CR/LF */ + if (memchr(buf, 0x00, size * 2) != NULL) { + gint i; + guchar c1, c2; + + for (i = 0; i < size; i++) { + c1 = buf[i * 2] & 0xff; + c2 = buf[i * 2 + 1] & 0xff; + if (c1 == 0x00 && c2 == 0x0d) { /* UTF-16BE CR */ + i++; + if (i >= size) { + break; + } + c1 = buf[i * 2] & 0xff; + c2 = buf[i * 2 + 1] & 0xff; + if (c1 == 0x00 && c2 == 0x0a) { /* UTF-16BE LF */ + guess_enc = C_UTF_16BE; + break; + } + } else if (c1 == 0x0d && c2 == 0x00) { /* UTF-16LE CR */ + i++; + if (i >= size) { + break; + } + c1 = buf[i * 2] & 0xff; + c2 = buf[i * 2 + 1] & 0xff; + if (c1 == 0x0a && c2 == 0x00) { /* UTF-16LE LF */ + guess_enc = C_UTF_16LE; + break; + } + } else if (c1 == 0x00 && c2 == 0x0a) { /* UTF-16BE LF */ + guess_enc = C_UTF_16BE; + break; + } else if (c1 == 0x0a && c2 == 0x00) { /* UTF-16LE LF */ + guess_enc = C_UTF_16LE; + break; + } + } + + if (guess_enc != C_AUTO) { + debug_print("conv_check_file_encoding: %s detected\n", + conv_get_charset_str(guess_enc)); + fclose(fp); + return guess_enc; + } + } + } + + rewind(fp); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + gchar *str; + gint error = 0; + + if (is_locale) { + str = conv_codeset_strdup_full(buf, enc_str, + CS_INTERNAL, &error); + if (!str || error != 0) + is_locale = FALSE; + g_free(str); + } + + if (is_utf8 && g_utf8_validate(buf, -1, NULL) == FALSE) { + is_utf8 = FALSE; + } + + if (!is_locale && !is_utf8) + break; + } + + fclose(fp); + + if (is_locale) + return enc; + else if (is_utf8) + return C_UTF_8; + else + return C_AUTO; +} + +gchar *conv_filename_from_utf8(const gchar *utf8_file) +{ + gchar *fs_file; + GError *error = NULL; + + g_return_val_if_fail(utf8_file != NULL, NULL); + + fs_file = g_filename_from_utf8(utf8_file, -1, NULL, NULL, &error); + if (error) { + g_warning("failed to convert encoding of file name: %s\n", + error->message); + g_error_free(error); + } + if (!fs_file) + fs_file = g_strdup(utf8_file); + + return fs_file; +} + +gchar *conv_filename_to_utf8(const gchar *fs_file) +{ + gchar *utf8_file; + GError *error = NULL; + + g_return_val_if_fail(fs_file != NULL, NULL); + + utf8_file = g_filename_to_utf8(fs_file, -1, NULL, NULL, &error); + if (error) { + g_warning("failed to convert encoding of file name: %s\n", + error->message); + g_error_free(error); + } + if (!utf8_file) + utf8_file = g_strdup(fs_file); + + return utf8_file; +} diff --git a/libsylph/codeconv.h b/libsylph/codeconv.h new file mode 100644 index 0000000..5143fd4 --- /dev/null +++ b/libsylph/codeconv.h @@ -0,0 +1,267 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2017 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __CODECONV_H__ +#define __CODECONV_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +typedef struct _CodeConverter CodeConverter; + +typedef enum +{ + C_AUTO, + C_US_ASCII, + C_UTF_8, + C_UTF_7, + C_ISO_8859_1, + C_ISO_8859_2, + C_ISO_8859_3, + C_ISO_8859_4, + C_ISO_8859_5, + C_ISO_8859_6, + C_ISO_8859_7, + C_ISO_8859_8, + C_ISO_8859_9, + C_ISO_8859_10, + C_ISO_8859_11, + C_ISO_8859_13, + C_ISO_8859_14, + C_ISO_8859_15, + C_BALTIC, + C_CP932, + C_CP1250, + C_CP1251, + C_CP1252, + C_CP1253, + C_CP1254, + C_CP1255, + C_CP1256, + C_CP1257, + C_CP1258, + C_WINDOWS_932, + C_WINDOWS_1250, + C_WINDOWS_1251, + C_WINDOWS_1252, + C_WINDOWS_1253, + C_WINDOWS_1254, + C_WINDOWS_1255, + C_WINDOWS_1256, + C_WINDOWS_1257, + C_WINDOWS_1258, + C_KOI8_R, + C_KOI8_T, + C_KOI8_U, + C_ISO_2022_JP, + C_ISO_2022_JP_2, + C_ISO_2022_JP_3, + C_EUC_JP, + C_EUC_JP_MS, + C_SHIFT_JIS, + C_ISO_2022_KR, + C_EUC_KR, + C_ISO_2022_CN, + C_EUC_CN, + C_GB2312, + C_GBK, + C_EUC_TW, + C_BIG5, + C_BIG5_HKSCS, + C_TIS_620, + C_WINDOWS_874, + C_GEORGIAN_PS, + C_TCVN5712_1, + C_ISO_8859_16, + C_UTF_16, + C_UTF_16BE, + C_UTF_16LE +} CharSet; + +typedef enum +{ + C_AD_BY_LOCALE, + C_AD_NEVER, + C_AD_JAPANESE +} ConvADType; + +typedef gchar *(*CodeConvFunc) (const gchar *inbuf, gint *error); + +struct _CodeConverter +{ + CodeConvFunc code_conv_func; + gchar *src_encoding; + gchar *dest_encoding; +}; + +#define CS_AUTO "AUTO" +#define CS_US_ASCII "US-ASCII" +#define CS_ANSI_X3_4_1968 "ANSI_X3.4-1968" +#define CS_UTF_8 "UTF-8" +#define CS_UTF_7 "UTF-7" +#define CS_ISO_8859_1 "ISO-8859-1" +#define CS_ISO_8859_2 "ISO-8859-2" +#define CS_ISO_8859_3 "ISO-8859-3" +#define CS_ISO_8859_4 "ISO-8859-4" +#define CS_ISO_8859_5 "ISO-8859-5" +#define CS_ISO_8859_6 "ISO-8859-6" +#define CS_ISO_8859_7 "ISO-8859-7" +#define CS_ISO_8859_8 "ISO-8859-8" +#define CS_ISO_8859_9 "ISO-8859-9" +#define CS_ISO_8859_10 "ISO-8859-10" +#define CS_ISO_8859_11 "ISO-8859-11" +#define CS_ISO_8859_13 "ISO-8859-13" +#define CS_ISO_8859_14 "ISO-8859-14" +#define CS_ISO_8859_15 "ISO-8859-15" +#define CS_BALTIC "BALTIC" +#define CS_CP932 "CP932" +#define CS_CP1250 "CP1250" +#define CS_CP1251 "CP1251" +#define CS_CP1252 "CP1252" +#define CS_CP1253 "CP1253" +#define CS_CP1254 "CP1254" +#define CS_CP1255 "CP1255" +#define CS_CP1256 "CP1256" +#define CS_CP1257 "CP1257" +#define CS_CP1258 "CP1258" +#define CS_WINDOWS_932 "Windows-932" +#define CS_WINDOWS_1250 "Windows-1250" +#define CS_WINDOWS_1251 "Windows-1251" +#define CS_WINDOWS_1252 "Windows-1252" +#define CS_WINDOWS_1253 "Windows-1253" +#define CS_WINDOWS_1254 "Windows-1254" +#define CS_WINDOWS_1255 "Windows-1255" +#define CS_WINDOWS_1256 "Windows-1256" +#define CS_WINDOWS_1257 "Windows-1257" +#define CS_WINDOWS_1258 "Windows-1258" +#define CS_KOI8_R "KOI8-R" +#define CS_KOI8_T "KOI8-T" +#define CS_KOI8_U "KOI8-U" +#define CS_ISO_2022_JP "ISO-2022-JP" +#define CS_ISO_2022_JP_2 "ISO-2022-JP-2" +#define CS_ISO_2022_JP_3 "ISO-2022-JP-3" +#define CS_EUC_JP "EUC-JP" +#define CS_EUCJP "EUCJP" +#define CS_EUC_JP_MS "EUC-JP-MS" +#define CS_SHIFT_JIS "Shift_JIS" +#define CS_SHIFT__JIS "SHIFT-JIS" +#define CS_SJIS "SJIS" +#define CS_X_SJIS "X-SJIS" +#define CS_ISO_2022_KR "ISO-2022-KR" +#define CS_EUC_KR "EUC-KR" +#define CS_KS_C_5601_1987 "ks_c_5601-1987" +#define CS_ISO_2022_CN "ISO-2022-CN" +#define CS_EUC_CN "EUC-CN" +#define CS_GB2312 "GB2312" +#define CS_GBK "GBK" +#define CS_X_GBK "X-GBK" +#define CS_EUC_TW "EUC-TW" +#define CS_BIG5 "Big5" +#define CS_BIG5_HKSCS "BIG5-HKSCS" +#define CS_TIS_620 "TIS-620" +#define CS_WINDOWS_874 "Windows-874" +#define CS_GEORGIAN_PS "GEORGIAN-PS" +#define CS_TCVN5712_1 "TCVN5712-1" +#define CS_ISO_8859_16 "ISO-8859-16" +#define CS_UTF_16 "UTF-16" +#define CS_UTF_16BE "UTF-16BE" +#define CS_UTF_16LE "UTF-16LE" + +#define C_INTERNAL C_UTF_8 +#define CS_INTERNAL CS_UTF_8 + +//void conv_mb_alnum(gchar *str); + +CharSet conv_guess_ja_encoding (const gchar *str); + +gchar *conv_utf8todisp (const gchar *inbuf, + gint *error); +gchar *conv_localetodisp (const gchar *inbuf, + gint *error); + +CodeConverter *conv_code_converter_new (const gchar *src_encoding, + const gchar *dest_encoding); +void conv_code_converter_destroy (CodeConverter *conv); +gchar *conv_convert (CodeConverter *conv, + const gchar *inbuf); + +#define conv_codeset_strdup(inbuf, src_code, dest_code) \ + (conv_codeset_strdup_full(inbuf, src_code, dest_code, NULL)) + +gchar *conv_codeset_strdup_full (const gchar *inbuf, + const gchar *src_encoding, + const gchar *dest_encoding, + gint *error); + +CodeConvFunc conv_get_code_conv_func (const gchar *src_encoding, + const gchar *dest_encoding); + +gchar *conv_iconv_strdup (const gchar *inbuf, + const gchar *src_encoding, + const gchar *dest_encoding, + gint *error); +gchar *conv_iconv_strdup_with_cd (const gchar *inbuf, + iconv_t cd, + gint *error); + +const gchar *conv_get_charset_str (CharSet charset); +CharSet conv_get_charset_from_str (const gchar *charset); +CharSet conv_get_locale_charset (void); +const gchar *conv_get_locale_charset_str (void); +CharSet conv_get_internal_charset (void); +const gchar *conv_get_internal_charset_str (void); +CharSet conv_get_outgoing_charset (void); +const gchar *conv_get_outgoing_charset_str (void); +gboolean conv_is_multibyte_encoding (CharSet encoding); + +const gchar *conv_get_current_locale (void); +gboolean conv_is_ja_locale (void); + +void conv_set_autodetect_type (ConvADType type); +ConvADType conv_get_autodetect_type (void); + +gchar *conv_unmime_header (const gchar *str, + const gchar *default_encoding); +void conv_encode_header (gchar *dest, + gint len, + const gchar *src, + gint header_len, + gboolean addr_field, + const gchar *out_encoding); +gchar *conv_encode_filename (const gchar *src, + const gchar *param_name, + const gchar *out_encoding); + +gint conv_copy_file (const gchar *src, + const gchar *dest, + const gchar *src_encoding); +gint conv_copy_dir (const gchar *src, + const gchar *dest, + const gchar *src_encoding); + +CharSet conv_check_file_encoding (const gchar *file); + +gchar *conv_filename_from_utf8 (const gchar *utf8_file); +gchar *conv_filename_to_utf8 (const gchar *fs_file); + +#endif /* __CODECONV_H__ */ diff --git a/libsylph/customheader.c b/libsylph/customheader.c new file mode 100644 index 0000000..0f4d4a0 --- /dev/null +++ b/libsylph/customheader.c @@ -0,0 +1,233 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include + +#include "customheader.h" +#include "prefs.h" +#include "prefs_account.h" +#include "utils.h" + + +void custom_header_read_config(PrefsAccount *ac) +{ + gchar *rcpath; + FILE *fp; + gchar buf[PREFSBUFSIZE]; + CustomHeader *ch; + + debug_print("Reading custom header configuration...\n"); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + CUSTOM_HEADER_RC, NULL); + if ((fp = g_fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + g_free(rcpath); + ac->customhdr_list = NULL; + return; + } + g_free(rcpath); + + /* remove all previous headers list */ + while (ac->customhdr_list != NULL) { + ch = (CustomHeader *)ac->customhdr_list->data; + custom_header_free(ch); + ac->customhdr_list = g_slist_remove(ac->customhdr_list, ch); + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + ch = custom_header_read_str(buf); + if (ch) { + if (ch->account_id == ac->account_id) { + ac->customhdr_list = + g_slist_append(ac->customhdr_list, ch); + } else + custom_header_free(ch); + } + } + + fclose(fp); +} + +void custom_header_write_config(PrefsAccount *ac) +{ + gchar *rcpath; + PrefFile *pfile; + GSList *cur; + gchar buf[PREFSBUFSIZE]; + FILE * fp; + CustomHeader *ch; + + GSList *all_hdrs = NULL; + + debug_print("Writing custom header configuration...\n"); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + CUSTOM_HEADER_RC, NULL); + + if ((fp = g_fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + } else { + all_hdrs = NULL; + + while (fgets(buf, sizeof(buf), fp) != NULL) { + ch = custom_header_read_str(buf); + if (ch) { + if (ch->account_id != ac->account_id) + all_hdrs = + g_slist_append(all_hdrs, ch); + else + custom_header_free(ch); + } + } + + fclose(fp); + } + + if ((pfile = prefs_file_open(rcpath)) == NULL) { + g_warning("failed to write configuration to file\n"); + g_free(rcpath); + return; + } + + for (cur = all_hdrs; cur != NULL; cur = cur->next) { + CustomHeader *hdr = (CustomHeader *)cur->data; + gchar *chstr; + + chstr = custom_header_get_str(hdr); + if (fputs(chstr, pfile->fp) == EOF || + fputc('\n', pfile->fp) == EOF) { + FILE_OP_ERROR(rcpath, "fputs || fputc"); + prefs_file_close_revert(pfile); + g_free(rcpath); + g_free(chstr); + return; + } + g_free(chstr); + } + + for (cur = ac->customhdr_list; cur != NULL; cur = cur->next) { + CustomHeader *hdr = (CustomHeader *)cur->data; + gchar *chstr; + + chstr = custom_header_get_str(hdr); + if (fputs(chstr, pfile->fp) == EOF || + fputc('\n', pfile->fp) == EOF) { + FILE_OP_ERROR(rcpath, "fputs || fputc"); + prefs_file_close_revert(pfile); + g_free(rcpath); + g_free(chstr); + return; + } + g_free(chstr); + } + + g_free(rcpath); + + while (all_hdrs != NULL) { + ch = (CustomHeader *)all_hdrs->data; + custom_header_free(ch); + all_hdrs = g_slist_remove(all_hdrs, ch); + } + + if (prefs_file_close(pfile) < 0) { + g_warning("failed to write configuration to file\n"); + return; + } +} + +gchar *custom_header_get_str(CustomHeader *ch) +{ + return g_strdup_printf("%i:%s: %s", + ch->account_id, ch->name, + ch->value ? ch->value : ""); +} + +CustomHeader *custom_header_read_str(const gchar *buf) +{ + CustomHeader *ch; + gchar *account_id_str; + gint id; + gchar *name; + gchar *value; + gchar *tmp; + + Xstrdup_a(tmp, buf, return NULL); + + account_id_str = tmp; + + name = strchr(account_id_str, ':'); + if (!name) + return NULL; + else { + gchar *endp; + + *name++ = '\0'; + id = strtol(account_id_str, &endp, 10); + if (*endp != '\0') return NULL; + } + + value = strchr(name, ':'); + if (!value) return NULL; + + *value++ = '\0'; + + g_strstrip(name); + g_strstrip(value); + + ch = g_new0(CustomHeader, 1); + ch->account_id = id; + ch->name = *name ? g_strdup(name) : NULL; + ch->value = *value ? g_strdup(value) : NULL; + + return ch; +} + +CustomHeader *custom_header_find(GSList *header_list, const gchar *header) +{ + GSList *cur; + CustomHeader *chdr; + + for (cur = header_list; cur != NULL; cur = cur->next) { + chdr = (CustomHeader *)cur->data; + if (!g_ascii_strcasecmp(chdr->name, header)) + return chdr; + } + + return NULL; +} + +void custom_header_free(CustomHeader *ch) +{ + if (!ch) return; + + g_free(ch->name); + g_free(ch->value); + g_free(ch); +} diff --git a/libsylph/customheader.h b/libsylph/customheader.h new file mode 100644 index 0000000..66e6cc4 --- /dev/null +++ b/libsylph/customheader.h @@ -0,0 +1,45 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __CUSTOMHEADER_H__ +#define __CUSTOMHEADER_H__ + +#include + +typedef struct _CustomHeader CustomHeader; + +#include "prefs_account.h" + +struct _CustomHeader +{ + gint account_id; + gchar *name; + gchar *value; +}; + +void custom_header_read_config (PrefsAccount *ac); +void custom_header_write_config (PrefsAccount *ac); + +gchar *custom_header_get_str (CustomHeader *ch); +CustomHeader *custom_header_read_str (const gchar *buf); +CustomHeader *custom_header_find (GSList *header_list, + const gchar *header); +void custom_header_free (CustomHeader *ch); + +#endif /* __CUSTOMHEADER_H__ */ diff --git a/libsylph/defs.h b/libsylph/defs.h new file mode 100644 index 0000000..9e3f82b --- /dev/null +++ b/libsylph/defs.h @@ -0,0 +1,134 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2012 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __DEFS_H__ +#define __DEFS_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#if HAVE_PATHS_H +# include +#endif + +#if HAVE_SYS_PARAM_H +# include +#endif + +#define INBOX_DIR "inbox" +#define OUTBOX_DIR "sent" +#define QUEUE_DIR "queue" +#define DRAFT_DIR "draft" +#define TRASH_DIR "trash" +#define JUNK_DIR "junk" +#if defined(G_OS_WIN32) || defined(__APPLE__) +# define RC_DIR "Sylpheed" +#else +# define RC_DIR ".sylpheed-2.0" +#endif +#define OLD_RC_DIR ".sylpheed" +#define NEWS_CACHE_DIR "newscache" +#define IMAP_CACHE_DIR "imapcache" +#define MIME_TMP_DIR "mimetmp" +#define COMMON_RC "sylpheedrc" +#define ACCOUNT_RC "accountrc" +#define FILTER_RC "filterrc" +#define FILTER_LIST "filter.xml" +#define FILTER_HEADER_RC "filterheaderrc" +#define CUSTOM_HEADER_RC "customheaderrc" +#define DISPLAY_HEADER_RC "dispheaderrc" +#define MENU_RC "menurc" +#define ACTIONS_RC "actionsrc" +#define COMMAND_HISTORY "command_history" +#define TEMPLATE_DIR "templates" +#define TMP_DIR "tmp" +#define UIDL_DIR "uidl" +#define PLUGIN_DIR "plugins" +#define NEWSGROUP_LIST ".newsgroup_list" +#define ADDRESS_BOOK "addressbook.xml" +#define MANUAL_HTML_INDEX "sylpheed.html" +#define FAQ_HTML_INDEX "sylpheed-faq.html" +#define HOMEPAGE_URI "http://sylpheed.sraoss.jp/" +#define DOWNLOAD_URI "http://sylpheed.sraoss.jp/download.php" +#define VERSION_URI "http://sylpheed.sraoss.jp/version.txt" +#define PLUGIN_HOMEPAGE_URI "http://sylpheed.sraoss.jp/en/plugin.html" +#define PLUGIN_VERSION_URI "http://sylpheed.sraoss.jp/plugin_version.txt" +#define FOLDER_LIST "folderlist.xml" +#define CACHE_FILE ".sylpheed_cache" +#define MARK_FILE ".sylpheed_mark" +#define SEARCH_CACHE "search_cache" +#define CACHE_VERSION 0x21 +#define MARK_VERSION 2 +#define SEARCH_CACHE_VERSION 1 + +#ifdef G_OS_WIN32 +# define REMOTE_CMD_PORT 50215 +#endif + +#ifdef G_OS_WIN32 +# define DEFAULT_SIGNATURE "signature.txt" +#else +# define DEFAULT_SIGNATURE ".signature" +#endif +#define DEFAULT_INC_PATH "/usr/bin/mh/inc" +#define DEFAULT_INC_PROGRAM "inc" +/* #define DEFAULT_INC_PATH "/usr/bin/imget" */ +/* #define DEFAULT_INC_PROGRAM "imget" */ +#define DEFAULT_SENDMAIL_CMD "/usr/sbin/sendmail -t -i" +#ifdef __APPLE__ +# define DEFAULT_BROWSER_CMD "open '%s'" +#else +# define DEFAULT_BROWSER_CMD "xdg-open '%s'" +#endif + +#ifdef _PATH_MAILDIR +# define DEFAULT_SPOOL_PATH _PATH_MAILDIR +#else +# define DEFAULT_SPOOL_PATH "/var/spool/mail" +#endif + +#define BUFFSIZE 8192 + +#ifndef MAXPATHLEN +# define MAXPATHLEN 4095 +#endif + +#define DEFAULT_HEIGHT 460 +#define DEFAULT_FOLDERVIEW_WIDTH 179 +#define DEFAULT_MAINVIEW_WIDTH 600 +#define DEFAULT_SUMMARY_HEIGHT 140 +#define DEFAULT_HEADERVIEW_HEIGHT 40 +#define DEFAULT_COMPOSE_HEIGHT 560 +#define BORDER_WIDTH 2 +#define CTREE_INDENT 18 +#define FOLDER_SPACING 4 +#define MAX_ENTRY_LENGTH 8191 +#define COLOR_DIM 35000 +#define UI_REFRESH_INTERVAL 50000 /* usec */ +#define FOLDER_UPDATE_INTERVAL 1500 /* msec */ +#define PROGRESS_UPDATE_INTERVAL 200 /* msec */ +#define SESSION_TIMEOUT_INTERVAL 60 /* sec */ +#define MAX_HISTORY_SIZE 16 + +#define DEFAULT_MESSAGE_FONT "Monospace 12" + +#endif /* __DEFS_H__ */ diff --git a/libsylph/displayheader.c b/libsylph/displayheader.c new file mode 100644 index 0000000..edafc5a --- /dev/null +++ b/libsylph/displayheader.c @@ -0,0 +1,59 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "displayheader.h" + +gchar *display_header_prop_get_str(DisplayHeaderProp *dp) +{ + return g_strconcat(dp->hidden ? "-" : "", dp->name, NULL); +} + +DisplayHeaderProp *display_header_prop_read_str(gchar *buf) +{ + DisplayHeaderProp *dp; + + dp = g_new0(DisplayHeaderProp, 1); + + dp->hidden = FALSE; + if (*buf == '-') { + dp->hidden = TRUE; + buf++; + } + if (*buf == '\0') { + g_free(dp); + return NULL; + } + dp->name = g_strdup(buf); + + return dp; +} + +void display_header_prop_free(DisplayHeaderProp *dp) +{ + if (!dp) return; + + g_free(dp->name); + g_free(dp); +} diff --git a/libsylph/displayheader.h b/libsylph/displayheader.h new file mode 100644 index 0000000..4ca8e18 --- /dev/null +++ b/libsylph/displayheader.h @@ -0,0 +1,37 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __DISPLAYHEADER_H__ +#define __DISPLAYHEADER_H__ + +#include + +typedef struct _DisplayHeaderProp DisplayHeaderProp; + +struct _DisplayHeaderProp +{ + gchar *name; + gboolean hidden; +}; + +gchar *display_header_prop_get_str (DisplayHeaderProp *dp); +DisplayHeaderProp *display_header_prop_read_str (gchar *buf); +void display_header_prop_free (DisplayHeaderProp *dp); + +#endif /* __DISPLAYHEADER_H__ */ diff --git a/libsylph/enums.h b/libsylph/enums.h new file mode 100644 index 0000000..29bae62 --- /dev/null +++ b/libsylph/enums.h @@ -0,0 +1,66 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __ENUMS_H__ +#define __ENUMS_H__ + +typedef enum +{ + TOOLBAR_NONE, + TOOLBAR_ICON, + TOOLBAR_TEXT, + TOOLBAR_BOTH, + TOOLBAR_BOTH_HORIZ +} ToolbarStyle; + +typedef enum +{ + LAYOUT_NORMAL, + LAYOUT_VERTICAL, + LAYOUT_VERTICAL_DOUBLE, + LAYOUT_WIDE_MESSAGE, + LAYOUT_WIDE_SUMMARY +} LayoutType; + +typedef enum +{ + S_COL_MARK, + S_COL_UNREAD, + S_COL_MIME, + S_COL_SUBJECT, + S_COL_FROM, + S_COL_DATE, + S_COL_SIZE, + S_COL_NUMBER, + S_COL_TO, + + S_COL_MSG_INFO, + + S_COL_LABEL, + S_COL_TDATE, + + S_COL_FOREGROUND, + S_COL_BOLD, + + N_SUMMARY_COLS +} SummaryColumnType; + +#define N_SUMMARY_VISIBLE_COLS S_COL_MSG_INFO + +#endif /* __ENUMS_H__ */ diff --git a/libsylph/filter.c b/libsylph/filter.c new file mode 100644 index 0000000..c58f8fc --- /dev/null +++ b/libsylph/filter.c @@ -0,0 +1,1951 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2015 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#if USE_ONIGURUMA +# include +#elif HAVE_REGEX_H +# include +#endif +#include + +#include "filter.h" +#include "procmsg.h" +#include "procheader.h" +#include "folder.h" +#include "utils.h" +#include "xml.h" +#include "prefs.h" +#include "prefs_common.h" +#include "prefs_account.h" +#include "account.h" + +typedef enum +{ + FLT_O_CONTAIN = 1 << 0, + FLT_O_CASE_SENS = 1 << 1, + FLT_O_REGEX = 1 << 2 +} FilterOldFlag; + +static FilterInAddressBookFunc default_addrbook_func = NULL; + +static gboolean filter_match_cond (FilterCond *cond, + MsgInfo *msginfo, + GSList *hlist, + FilterInfo *fltinfo); +static gboolean filter_match_header_cond(FilterCond *cond, + GSList *hlist); +static gboolean filter_match_in_addressbook + (FilterCond *cond, + GSList *hlist, + FilterInfo *fltinfo); + +static void filter_cond_free (FilterCond *cond); +static void filter_action_free (FilterAction *action); + + +gint filter_apply(GSList *fltlist, const gchar *file, FilterInfo *fltinfo) +{ + MsgInfo *msginfo; + gint ret = 0; + + g_return_val_if_fail(file != NULL, -1); + g_return_val_if_fail(fltinfo != NULL, -1); + + if (!fltlist) return 0; + + msginfo = procheader_parse_file(file, fltinfo->flags, FALSE); + if (!msginfo) return 0; + msginfo->file_path = g_strdup(file); + + /* inherit MIME flag */ + fltinfo->flags.tmp_flags = + (fltinfo->flags.tmp_flags & ~MSG_CACHED_FLAG_MASK) | + (msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK); + + ret = filter_apply_msginfo(fltlist, msginfo, fltinfo); + + procmsg_msginfo_free(msginfo); + + return ret; +} + +gint filter_apply_msginfo(GSList *fltlist, MsgInfo *msginfo, + FilterInfo *fltinfo) +{ + gchar *file; + GSList *hlist, *cur; + FilterRule *rule; + gint ret = 0; + + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(fltinfo != NULL, -1); + + fltinfo->error = FLT_ERROR_OK; + + if (!fltlist) return 0; + + file = procmsg_get_message_file(msginfo); + if (!file) + return -1; + hlist = procheader_get_header_list_from_file(file); + if (!hlist) { + g_free(file); + return 0; + } + + procmsg_set_auto_decrypt_message(FALSE); + + for (cur = fltlist; cur != NULL; cur = cur->next) { + gboolean matched; + + rule = (FilterRule *)cur->data; + if (!rule->enabled) continue; + matched = filter_match_rule(rule, msginfo, hlist, fltinfo); + if (fltinfo->error != FLT_ERROR_OK) { + g_warning("filter_match_rule() returned error (code: %d)\n", fltinfo->error); + } + if (matched) { + debug_print("filter-log: %s: rule [%s] matched\n", + G_STRFUNC, rule->name ? rule->name : "(No name)"); + ret = filter_action_exec(rule, msginfo, file, fltinfo); + if (ret < 0) { + g_warning("filter_action_exec() returned error (code: %d)\n", fltinfo->error); + break; + } + if (fltinfo->drop_done == TRUE || + fltinfo->actions[FLT_ACTION_STOP_EVAL] == TRUE) + break; + } + } + + procmsg_set_auto_decrypt_message(TRUE); + + procheader_header_list_destroy(hlist); + g_free(file); + + return ret; +} + +gint filter_action_exec(FilterRule *rule, MsgInfo *msginfo, const gchar *file, + FilterInfo *fltinfo) +{ + FolderItem *dest_folder = NULL; + FilterAction *action; + GSList *cur; + gchar *cmdline; + gboolean copy_to_self = FALSE; + gint ret; + + g_return_val_if_fail(rule != NULL, -1); + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(file != NULL, -1); + g_return_val_if_fail(fltinfo != NULL, -1); + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + + switch (action->type) { + case FLT_ACTION_MARK: + debug_print("filter_action_exec(): mark\n"); + MSG_SET_PERM_FLAGS(fltinfo->flags, MSG_MARKED); + fltinfo->actions[action->type] = TRUE; + break; + case FLT_ACTION_COLOR_LABEL: + debug_print("filter_action_exec(): color label: %d\n", + action->int_value); + MSG_UNSET_PERM_FLAGS(fltinfo->flags, + MSG_CLABEL_FLAG_MASK); + MSG_SET_COLORLABEL_VALUE(fltinfo->flags, + action->int_value); + fltinfo->actions[action->type] = TRUE; + break; + case FLT_ACTION_MARK_READ: + debug_print("filter_action_exec(): mark as read\n"); + if (msginfo->folder) { + if (MSG_IS_NEW(fltinfo->flags)) + msginfo->folder->new--; + if (MSG_IS_UNREAD(fltinfo->flags)) + msginfo->folder->unread--; + } + MSG_UNSET_PERM_FLAGS(fltinfo->flags, MSG_NEW|MSG_UNREAD); + fltinfo->actions[action->type] = TRUE; + break; + case FLT_ACTION_EXEC: + cmdline = g_strconcat(action->str_value, " \"", file, + "\"", NULL); + ret = execute_command_line(cmdline, FALSE); + fltinfo->last_exec_exit_status = ret; + if (ret == -1) { + fltinfo->error = FLT_ERROR_EXEC_FAILED; + g_warning("filter_action_exec: cannot execute command: %s", cmdline); + g_free(cmdline); + return -1; + } + g_free(cmdline); + fltinfo->actions[action->type] = TRUE; + break; + case FLT_ACTION_EXEC_ASYNC: + cmdline = g_strconcat(action->str_value, " \"", file, + "\"", NULL); + ret = execute_command_line(cmdline, TRUE); + fltinfo->last_exec_exit_status = ret; + if (ret == -1) { + fltinfo->error = FLT_ERROR_EXEC_FAILED; + g_warning("filter_action_exec: cannot execute command: %s", cmdline); + g_free(cmdline); + return -1; + } + g_free(cmdline); + fltinfo->actions[action->type] = TRUE; + break; + default: + break; + } + } + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + + switch (action->type) { + case FLT_ACTION_MOVE: + case FLT_ACTION_COPY: + dest_folder = folder_find_item_from_identifier + (action->str_value); + if (!dest_folder) { + g_warning("dest folder '%s' not found\n", + action->str_value); + fltinfo->error = FLT_ERROR_ERROR; + return -1; + } + debug_print("filter_action_exec(): %s: dest_folder = %s\n", + action->type == FLT_ACTION_COPY ? + "copy" : "move", action->str_value); + + if (msginfo->folder) { + gint val; + + /* local filtering */ + if (msginfo->folder == dest_folder) + copy_to_self = TRUE; + else { + if (action->type == FLT_ACTION_COPY) { + MsgFlags save_flags; + + save_flags = msginfo->flags; + msginfo->flags = fltinfo->flags; + val = folder_item_copy_msg + (dest_folder, msginfo); + msginfo->flags = save_flags; + if (val == -1) { + fltinfo->error = FLT_ERROR_ERROR; + return -1; + } + } + fltinfo->actions[action->type] = TRUE; + } + } else { + MsgFlags save_flags; + + save_flags = msginfo->flags; + msginfo->flags = fltinfo->flags; + if (folder_item_add_msg_msginfo + (dest_folder, msginfo, FALSE) < 0) { + msginfo->flags = save_flags; + fltinfo->error = FLT_ERROR_ERROR; + return -1; + } + msginfo->flags = save_flags; + fltinfo->actions[action->type] = TRUE; + } + + fltinfo->dest_list = g_slist_append(fltinfo->dest_list, + dest_folder); + if (action->type == FLT_ACTION_MOVE) { + fltinfo->move_dest = dest_folder; + fltinfo->drop_done = TRUE; + } + break; + default: + break; + } + } + + if (fltinfo->drop_done == TRUE) + return 0; + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + + switch (action->type) { + case FLT_ACTION_NOT_RECEIVE: + debug_print("filter_action_exec(): don't receive\n"); + fltinfo->drop_done = TRUE; + fltinfo->actions[action->type] = TRUE; + return 0; + case FLT_ACTION_DELETE: + debug_print("filter_action_exec(): delete\n"); + if (msginfo->folder) { + /* local filtering */ + if (copy_to_self == FALSE) + fltinfo->actions[action->type] = TRUE; + } else + fltinfo->actions[action->type] = TRUE; + + fltinfo->drop_done = TRUE; + return 0; + case FLT_ACTION_STOP_EVAL: + debug_print("filter_action_exec(): stop evaluation\n"); + fltinfo->actions[action->type] = TRUE; + return 0; + default: + break; + } + } + + return 0; +} + +static gboolean strmatch_regex(const gchar *haystack, const gchar *needle) +{ +#ifdef USE_ONIGURUMA + gint ret = 0; + OnigRegex reg; + OnigErrorInfo err_info; + const UChar *ptn = (const UChar *)needle; + const UChar *str = (const UChar *)haystack; + size_t haystack_len; + + ret = onig_new(®, ptn, ptn + strlen(needle), + /* ONIG_OPTION_EXTEND requires spaces to be escaped */ + /* ONIG_OPTION_IGNORECASE|ONIG_OPTION_EXTEND, */ + ONIG_OPTION_IGNORECASE, + ONIG_ENCODING_UTF8, ONIG_SYNTAX_POSIX_EXTENDED, + &err_info); + if (ret != ONIG_NORMAL) { + g_warning("strmatch_regex: onig_new() failed: %d", ret); + return FALSE; + } + + haystack_len = strlen(haystack); + ret = onig_search(reg, str, str + haystack_len, + str, str + haystack_len, NULL, 0); + onig_free(reg); + + if (ret >= 0) + return TRUE; + else + return FALSE; +#elif defined(HAVE_REGCOMP) + gint ret = 0; + regex_t preg; + + ret = regcomp(&preg, needle, REG_ICASE|REG_EXTENDED); + if (ret != 0) + return FALSE; + + ret = regexec(&preg, haystack, 0, NULL, 0); + regfree(&preg); + + if (ret == 0) + return TRUE; + else + return FALSE; +#else + return FALSE; +#endif +} + +gboolean filter_match_rule(FilterRule *rule, MsgInfo *msginfo, GSList *hlist, + FilterInfo *fltinfo) +{ + FilterCond *cond; + GSList *cur; + gboolean matched; + + g_return_val_if_fail(rule->cond_list != NULL, FALSE); + + switch (rule->timing) { + case FLT_TIMING_ANY: + break; + case FLT_TIMING_ON_RECEIVE: + if (msginfo->folder != NULL) + return FALSE; + break; + case FLT_TIMING_MANUAL: + if (msginfo->folder == NULL) + return FALSE; + break; + default: + break; + } + + if (rule->bool_op == FLT_AND) { + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + cond = (FilterCond *)cur->data; + if (cond->type >= FLT_COND_SIZE_GREATER) { + matched = filter_match_cond + (cond, msginfo, hlist, fltinfo); + if (matched == FALSE) + return FALSE; + } + } + + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + cond = (FilterCond *)cur->data; + if (cond->type <= FLT_COND_TO_OR_CC) { + matched = filter_match_cond + (cond, msginfo, hlist, fltinfo); + if (matched == FALSE) + return FALSE; + } + } + + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + cond = (FilterCond *)cur->data; + if (cond->type == FLT_COND_BODY || + cond->type == FLT_COND_CMD_TEST) { + matched = filter_match_cond + (cond, msginfo, hlist, fltinfo); + if (matched == FALSE) + return FALSE; + } + } + + return TRUE; + } else if (rule->bool_op == FLT_OR) { + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + cond = (FilterCond *)cur->data; + if (cond->type >= FLT_COND_SIZE_GREATER) { + matched = filter_match_cond + (cond, msginfo, hlist, fltinfo); + if (matched == TRUE) + return TRUE; + } + } + + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + cond = (FilterCond *)cur->data; + if (cond->type <= FLT_COND_TO_OR_CC) { + matched = filter_match_cond + (cond, msginfo, hlist, fltinfo); + if (matched == TRUE) + return TRUE; + } + } + + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + cond = (FilterCond *)cur->data; + if (cond->type == FLT_COND_BODY || + cond->type == FLT_COND_CMD_TEST) { + matched = filter_match_cond + (cond, msginfo, hlist, fltinfo); + if (matched == TRUE) + return TRUE; + } + } + + return FALSE; + } + + return FALSE; +} + +static gboolean filter_match_cond(FilterCond *cond, MsgInfo *msginfo, + GSList *hlist, FilterInfo *fltinfo) +{ + gint ret; + gboolean matched = FALSE; + gboolean not_match = FALSE; + gint64 timediff = 0; + gchar *file; + gchar *cmdline; + PrefsAccount *cond_ac; + + switch (cond->type) { + case FLT_COND_HEADER: + if (cond->match_type == FLT_IN_ADDRESSBOOK) + return filter_match_in_addressbook(cond, hlist, fltinfo); + else + return filter_match_header_cond(cond, hlist); + case FLT_COND_ANY_HEADER: + return filter_match_header_cond(cond, hlist); + case FLT_COND_TO_OR_CC: + if (cond->match_type == FLT_IN_ADDRESSBOOK) + return filter_match_in_addressbook(cond, hlist, fltinfo); + else + return filter_match_header_cond(cond, hlist); + case FLT_COND_BODY: + matched = procmime_find_string(msginfo, cond->str_value, + cond->match_func); + break; + case FLT_COND_CMD_TEST: + file = procmsg_get_message_file(msginfo); + if (!file) + return FALSE; + cmdline = g_strconcat(cond->str_value, " \"", file, "\"", NULL); + ret = execute_command_line_async_wait(cmdline); + fltinfo->last_exec_exit_status = ret; + matched = (ret == 0); + if (ret == -1) + fltinfo->error = FLT_ERROR_EXEC_FAILED; + g_free(cmdline); + g_free(file); + break; + case FLT_COND_SIZE_GREATER: + matched = (msginfo->size > cond->int_value * 1024); + break; + case FLT_COND_AGE_GREATER: + timediff = time(NULL) - msginfo->date_t; + matched = (timediff > cond->int_value * 24 * 60 * 60); + break; + case FLT_COND_UNREAD: + matched = MSG_IS_UNREAD(msginfo->flags); + break; + case FLT_COND_MARK: + matched = MSG_IS_MARKED(msginfo->flags); + break; + case FLT_COND_COLOR_LABEL: + matched = (MSG_GET_COLORLABEL_VALUE(msginfo->flags) != 0); + break; + case FLT_COND_MIME: + matched = MSG_IS_MIME(msginfo->flags); + break; + case FLT_COND_ACCOUNT: + cond_ac = account_find_from_id(cond->int_value); + matched = (cond_ac != NULL && cond_ac == fltinfo->account); + break; + default: + g_warning("filter_match_cond(): unknown condition: %d\n", + cond->type); + fltinfo->error = FLT_ERROR_ERROR; + return FALSE; + } + + if (FLT_IS_NOT_MATCH(cond->match_flag)) { + not_match = TRUE; + matched = !matched; + } + + if (matched && get_debug_mode()) { + gchar *sv = cond->str_value ? cond->str_value : ""; + gchar *nm = not_match ? " (reverse match)" : ""; + + switch (cond->type) { + case FLT_COND_BODY: + debug_print("filter-log: %s: BODY, str_value: [%s]%s\n", G_STRFUNC, sv, nm); + break; + case FLT_COND_CMD_TEST: + debug_print("filter-log: %s: CMD_TEST, str_value: [%s]%s\n", G_STRFUNC, sv, nm); + break; + case FLT_COND_SIZE_GREATER: + debug_print("filter-log: %s: SIZE_GREATER: %u %s %d (KB)%s\n", G_STRFUNC, msginfo->size, not_match ? "<=" : ">", cond->int_value, nm); + break; + case FLT_COND_AGE_GREATER: + debug_print("filter-log: %s: AGE_GREATER: %lld (sec) %s %d (day)%s\n", G_STRFUNC, timediff, not_match ? "<=" : ">", cond->int_value, nm); + break; + case FLT_COND_UNREAD: + debug_print("filter-log: %s: UNREAD%s\n", G_STRFUNC, nm); + break; + case FLT_COND_MARK: + debug_print("filter-log: %s: MARK%s\n", G_STRFUNC, nm); + break; + case FLT_COND_COLOR_LABEL: + debug_print("filter-log: %s: COLOR_LABEL%s\n", G_STRFUNC, nm); + break; + case FLT_COND_MIME: + debug_print("filter-log: %s: MIME%s\n", G_STRFUNC, nm); + break; + case FLT_COND_ACCOUNT: + debug_print("filter-log: %s: ACCOUNT [%d]%s\n", G_STRFUNC, cond->int_value, nm); + break; + default: + break; + } + } + + return matched; +} + +static gboolean filter_match_header_cond(FilterCond *cond, GSList *hlist) +{ + gboolean matched = FALSE; + gboolean not_match = FALSE; + GSList *cur; + Header *header; + + for (cur = hlist; cur != NULL; cur = cur->next) { + header = (Header *)cur->data; + + switch (cond->type) { + case FLT_COND_HEADER: + if (!g_ascii_strcasecmp + (header->name, cond->header_name)) { + if (!cond->str_value || + cond->match_func(header->body, + cond->str_value)) + matched = TRUE; + } + break; + case FLT_COND_ANY_HEADER: + if (!cond->str_value || + cond->match_func(header->body, cond->str_value)) + matched = TRUE; + break; + case FLT_COND_TO_OR_CC: + if (!g_ascii_strcasecmp(header->name, "To") || + !g_ascii_strcasecmp(header->name, "Cc")) { + if (!cond->str_value || + cond->match_func(header->body, + cond->str_value)) + matched = TRUE; + } + break; + default: + break; + } + + if (matched == TRUE) + break; + } + + if (FLT_IS_NOT_MATCH(cond->match_flag)) { + not_match = TRUE; + matched = !matched; + } + + if (matched && get_debug_mode()) { + gchar *sv = cond->str_value ? cond->str_value : ""; + gchar *nm = not_match ? " (reverse match)" : ""; + + switch (cond->type) { + case FLT_COND_HEADER: + debug_print("filter-log: %s: HEADER [%s], str_value: [%s]%s\n", G_STRFUNC, cond->header_name, sv, nm); + break; + case FLT_COND_ANY_HEADER: + debug_print("filter-log: %s: ANY_HEADER, str_value: [%s]%s\n", G_STRFUNC, sv, nm); + break; + case FLT_COND_TO_OR_CC: + debug_print("filter-log: %s: TO_OR_CC, str_value: [%s]%s\n", G_STRFUNC, sv, nm); + break; + default: + break; + } + } + + return matched; +} + +static gboolean filter_match_in_addressbook(FilterCond *cond, GSList *hlist, + FilterInfo *fltinfo) +{ + gboolean matched = FALSE; + gboolean not_match = FALSE; + GSList *cur; + Header *header; + + if (!default_addrbook_func) + return FALSE; + if (cond->type != FLT_COND_HEADER && cond->type != FLT_COND_TO_OR_CC) + return FALSE; + + for (cur = hlist; cur != NULL; cur = cur->next) { + header = (Header *)cur->data; + + if (cond->type == FLT_COND_HEADER) { + if (!g_ascii_strcasecmp + (header->name, cond->header_name)) { + if (default_addrbook_func(header->body)) + matched = TRUE; + } + } else if (cond->type == FLT_COND_TO_OR_CC) { + if (!g_ascii_strcasecmp(header->name, "To") || + !g_ascii_strcasecmp(header->name, "Cc")) { + if (default_addrbook_func(header->body)) + matched = TRUE; + } + } + + if (matched == TRUE) + break; + } + + if (FLT_IS_NOT_MATCH(cond->match_flag)) { + not_match = TRUE; + matched = !matched; + } + + if (matched && get_debug_mode()) { + gchar *nm = not_match ? " (reverse match)" : ""; + + switch (cond->type) { + case FLT_COND_HEADER: + debug_print("filter-log: %s: HEADER [%s], IN_ADDRESSBOOK%s\n", G_STRFUNC, cond->header_name, nm); + break; + case FLT_COND_TO_OR_CC: + debug_print("filter-log: %s: TO_OR_CC, IN_ADDRESSBOOK%s\n", G_STRFUNC, nm); + break; + default: + break; + } + } + + return matched; +} + +gboolean filter_rule_requires_full_headers(FilterRule *rule) +{ + GSList *cur; + + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + FilterCond *cond = (FilterCond *)cur->data; + const gchar *name = cond->header_name; + + if (cond->type == FLT_COND_HEADER && name) { + if (g_ascii_strcasecmp(name, "Date") != 0 && + g_ascii_strcasecmp(name, "From") != 0 && + g_ascii_strcasecmp(name, "To") != 0 && + g_ascii_strcasecmp(name, "Newsgroups") != 0 && + g_ascii_strcasecmp(name, "Subject") != 0) + return TRUE; + } else if (cond->type == FLT_COND_ANY_HEADER || + cond->type == FLT_COND_TO_OR_CC) + return TRUE; + } + + return FALSE; +} + +#define RETURN_IF_TAG_NOT_MATCH(tag_name) \ + if (strcmp2(xmlnode->tag->tag, tag_name) != 0) { \ + g_warning("tag name != \"" tag_name "\"\n"); \ + filter_cond_list_free(cond_list); \ + filter_action_list_free(cond_list); \ + return FALSE; \ + } \ + +#define STR_SWITCH(sw) { const gchar *sw_str = sw; +#define STR_CASE_BEGIN(str) if (!strcmp(sw_str, str)) { +#define STR_CASE(str) } else if (!strcmp(sw_str, str)) { +#define STR_CASE_END } } + +static gboolean filter_xml_node_func(GNode *node, gpointer data) +{ + XMLNode *xmlnode; + GList *list; + const gchar *name = NULL; + FilterTiming timing = FLT_TIMING_ANY; + gboolean enabled = TRUE; + FilterRule *rule; + FilterBoolOp bool_op = FLT_OR; + const gchar *target_folder = NULL; + gboolean recursive = FALSE; + GSList *cond_list = NULL; + GSList *action_list = NULL; + GNode *child, *cond_child, *action_child; + GSList **fltlist = (GSList **)data; + + if (g_node_depth(node) != 2) return FALSE; + g_return_val_if_fail(node->data != NULL, FALSE); + + xmlnode = node->data; + RETURN_IF_TAG_NOT_MATCH("rule"); + + for (list = xmlnode->tag->attr; list != NULL; list = list->next) { + XMLAttr *attr = (XMLAttr *)list->data; + + if (!attr || !attr->name || !attr->value) continue; + if (!strcmp(attr->name, "name")) + name = attr->value; + else if (!strcmp(attr->name, "timing")) { + if (!strcmp(attr->value, "any")) + timing = FLT_TIMING_ANY; + else if (!strcmp(attr->value, "receive")) + timing = FLT_TIMING_ON_RECEIVE; + else if (!strcmp(attr->value, "manual")) + timing = FLT_TIMING_MANUAL; + } else if (!strcmp(attr->name, "enabled")) { + if (!strcmp(attr->value, "true")) + enabled = TRUE; + else + enabled = FALSE; + } + } + + /* condition list */ + child = node->children; + if (!child) { + g_warning(" must have children\n"); + return FALSE; + } + xmlnode = child->data; + RETURN_IF_TAG_NOT_MATCH("condition-list"); + for (list = xmlnode->tag->attr; list != NULL; list = list->next) { + XMLAttr *attr = (XMLAttr *)list->data; + + if (attr && attr->name && attr->value && + !strcmp(attr->name, "bool")) { + if (!strcmp(attr->value, "or")) + bool_op = FLT_OR; + else + bool_op = FLT_AND; + } + } + + for (cond_child = child->children; cond_child != NULL; + cond_child = cond_child->next) { + const gchar *type = NULL; + const gchar *name = NULL; + const gchar *value = NULL; + gboolean case_sens = FALSE; + FilterCond *cond; + FilterCondType cond_type = FLT_COND_HEADER; + FilterMatchType match_type = FLT_CONTAIN; + FilterMatchFlag match_flag = 0; + + xmlnode = cond_child->data; + if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue; + + for (list = xmlnode->tag->attr; list != NULL; list = list->next) { + XMLAttr *attr = (XMLAttr *)list->data; + + if (!attr || !attr->name || !attr->value) continue; + + STR_SWITCH(attr->name) + STR_CASE_BEGIN("type") + type = attr->value; + STR_CASE("name") + name = attr->value; + STR_CASE("case") + case_sens = TRUE; + STR_CASE("recursive") + if (!strcmp(attr->value, "true")) + recursive = TRUE; + else + recursive = FALSE; + STR_CASE_END + } + + if (type) { + filter_rule_match_type_str_to_enum + (type, &match_type, &match_flag); + } + if (case_sens) + match_flag |= FLT_CASE_SENS; + value = xmlnode->element; + + STR_SWITCH(xmlnode->tag->tag) + STR_CASE_BEGIN("match-header") + cond_type = FLT_COND_HEADER; + STR_CASE("match-any-header") + cond_type = FLT_COND_ANY_HEADER; + STR_CASE("match-to-or-cc") + cond_type = FLT_COND_TO_OR_CC; + STR_CASE("match-body-text") + cond_type = FLT_COND_BODY; + STR_CASE("command-test") + cond_type = FLT_COND_CMD_TEST; + STR_CASE("size") + cond_type = FLT_COND_SIZE_GREATER; + STR_CASE("age") + cond_type = FLT_COND_AGE_GREATER; + STR_CASE("unread") + cond_type = FLT_COND_UNREAD; + STR_CASE("mark") + cond_type = FLT_COND_MARK; + STR_CASE("color-label") + cond_type = FLT_COND_COLOR_LABEL; + STR_CASE("mime") + cond_type = FLT_COND_MIME; + STR_CASE("account-id") + cond_type = FLT_COND_ACCOUNT; + STR_CASE("target-folder") + target_folder = value; + continue; + STR_CASE_END + + cond = filter_cond_new(cond_type, match_type, match_flag, + name, value); + cond_list = g_slist_append(cond_list, cond); + } + + /* action list */ + child = child->next; + if (!child) { + g_warning(" must have multiple children\n"); + filter_cond_list_free(cond_list); + return FALSE; + } + xmlnode = child->data; + RETURN_IF_TAG_NOT_MATCH("action-list"); + + for (action_child = child->children; action_child != NULL; + action_child = action_child->next) { + FilterAction *action; + FilterActionType action_type = FLT_ACTION_NONE; + + xmlnode = action_child->data; + if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue; + + STR_SWITCH(xmlnode->tag->tag) + STR_CASE_BEGIN("move") + action_type = FLT_ACTION_MOVE; + STR_CASE("copy") + action_type = FLT_ACTION_COPY; + STR_CASE("not-receive") + action_type = FLT_ACTION_NOT_RECEIVE; + STR_CASE("delete") + action_type = FLT_ACTION_DELETE; + STR_CASE("exec") + action_type = FLT_ACTION_EXEC; + STR_CASE("exec-async") + action_type = FLT_ACTION_EXEC_ASYNC; + STR_CASE("mark") + action_type = FLT_ACTION_MARK; + STR_CASE("color-label") + action_type = FLT_ACTION_COLOR_LABEL; + STR_CASE("mark-as-read") + action_type = FLT_ACTION_MARK_READ; + STR_CASE("forward") + action_type = FLT_ACTION_FORWARD; + STR_CASE("forward-as-attachment") + action_type = FLT_ACTION_FORWARD_AS_ATTACHMENT; + STR_CASE("redirect") + action_type = FLT_ACTION_REDIRECT; + STR_CASE("stop-eval") + action_type = FLT_ACTION_STOP_EVAL; + STR_CASE_END + + action = filter_action_new(action_type, xmlnode->element); + action_list = g_slist_append(action_list, action); + } + + if (name && cond_list) { + rule = filter_rule_new(name, bool_op, cond_list, action_list); + rule->timing = timing; + rule->enabled = enabled; + if (target_folder) { + rule->target_folder = g_strdup(target_folder); + rule->recursive = recursive; + } + *fltlist = g_slist_prepend(*fltlist, rule); + } + + return FALSE; +} + +#undef RETURN_IF_TAG_NOT_MATCH +#undef STR_SWITCH +#undef STR_CASE_BEGIN +#undef STR_CASE +#undef STR_CASE_END + +GSList *filter_xml_node_to_filter_list(GNode *node) +{ + GSList *fltlist = NULL; + + g_return_val_if_fail(node != NULL, NULL); + + g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2, + filter_xml_node_func, &fltlist); + fltlist = g_slist_reverse(fltlist); + + return fltlist; +} + +GSList *filter_read_file(const gchar *file) +{ + GNode *node; + GSList *list; + + g_return_val_if_fail(file != NULL, NULL); + + debug_print("Reading %s\n", file); + + if (!is_file_exist(file)) + return NULL; + + node = xml_parse_file(file); + if (!node) { + g_warning("Can't parse %s\n", file); + return NULL; + } + + list = filter_xml_node_to_filter_list(node); + + xml_free_tree(node); + + return list; +} + +void filter_read_config(void) +{ + gchar *rcpath; + + debug_print("Reading filter configuration...\n"); + + /* remove all previous filter list */ + while (prefs_common.fltlist != NULL) { + FilterRule *rule = (FilterRule *)prefs_common.fltlist->data; + + filter_rule_free(rule); + prefs_common.fltlist = g_slist_remove(prefs_common.fltlist, + rule); + } + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST, + NULL); + prefs_common.fltlist = filter_read_file(rcpath); + g_free(rcpath); +} + +#define NODE_NEW(tag, text) \ + node = xml_node_new(xml_tag_new(tag), text) +#define ADD_ATTR(name, value) \ + xml_tag_add_attr(node->tag, xml_attr_new(name, value)) + +void filter_write_file(GSList *list, const gchar *file) +{ + PrefFile *pfile; + GSList *cur; + + g_return_if_fail(file != NULL); + + if ((pfile = prefs_file_open(file)) == NULL) { + g_warning("failed to write filter configuration to file: %s\n", + file); + return; + } + + xml_file_put_xml_decl(pfile->fp); + fputs("\n\n", pfile->fp); + + for (cur = list; cur != NULL; cur = cur->next) { + FilterRule *rule = (FilterRule *)cur->data; + GSList *cur_cond; + GSList *cur_action; + gchar match_type[64]; + gchar nstr[16]; + + fputs(" fp); + xml_file_put_escape_str(pfile->fp, rule->name); + fprintf(pfile->fp, "\" timing=\"%s\"", + rule->timing == FLT_TIMING_ON_RECEIVE ? "receive" : + rule->timing == FLT_TIMING_MANUAL ? "manual" : "any"); + fprintf(pfile->fp, " enabled=\"%s\">\n", + rule->enabled ? "true" : "false"); + + fprintf(pfile->fp, " \n", + rule->bool_op == FLT_OR ? "or" : "and"); + + for (cur_cond = rule->cond_list; cur_cond != NULL; + cur_cond = cur_cond->next) { + FilterCond *cond = (FilterCond *)cur_cond->data; + XMLNode *node = NULL; + + switch (cond->match_type) { + case FLT_CONTAIN: + strncpy2(match_type, + FLT_IS_NOT_MATCH(cond->match_flag) + ? "not-contain" : "contains", + sizeof(match_type)); + break; + case FLT_EQUAL: + strncpy2(match_type, + FLT_IS_NOT_MATCH(cond->match_flag) + ? "is-not" : "is", sizeof(match_type)); + break; + case FLT_REGEX: + strncpy2(match_type, + FLT_IS_NOT_MATCH(cond->match_flag) + ? "not-regex" : "regex", + sizeof(match_type)); + break; + case FLT_IN_ADDRESSBOOK: + strncpy2(match_type, + FLT_IS_NOT_MATCH(cond->match_flag) + ? "not-in-addressbook" : "in-addressbook", + sizeof(match_type)); + break; + default: + match_type[0] = '\0'; + break; + } + + switch (cond->type) { + case FLT_COND_HEADER: + NODE_NEW("match-header", cond->str_value); + ADD_ATTR("type", match_type); + ADD_ATTR("name", cond->header_name); + if (FLT_IS_CASE_SENS(cond->match_flag)) + ADD_ATTR("case", "true"); + break; + case FLT_COND_ANY_HEADER: + NODE_NEW("match-any-header", cond->str_value); + ADD_ATTR("type", match_type); + if (FLT_IS_CASE_SENS(cond->match_flag)) + ADD_ATTR("case", "true"); + break; + case FLT_COND_TO_OR_CC: + NODE_NEW("match-to-or-cc", cond->str_value); + ADD_ATTR("type", match_type); + if (FLT_IS_CASE_SENS(cond->match_flag)) + ADD_ATTR("case", "true"); + break; + case FLT_COND_BODY: + NODE_NEW("match-body-text", cond->str_value); + ADD_ATTR("type", match_type); + if (FLT_IS_CASE_SENS(cond->match_flag)) + ADD_ATTR("case", "true"); + break; + case FLT_COND_CMD_TEST: + NODE_NEW("command-test", cond->str_value); + break; + case FLT_COND_SIZE_GREATER: + NODE_NEW("size", itos_buf(nstr, cond->int_value)); + ADD_ATTR("type", + FLT_IS_NOT_MATCH(cond->match_flag) + ? "lt" : "gt"); + break; + case FLT_COND_AGE_GREATER: + NODE_NEW("age", itos_buf(nstr, cond->int_value)); + ADD_ATTR("type", + FLT_IS_NOT_MATCH(cond->match_flag) + ? "lt" : "gt"); + break; + case FLT_COND_UNREAD: + NODE_NEW("unread", NULL); + ADD_ATTR("type", + FLT_IS_NOT_MATCH(cond->match_flag) + ? "is-not" : "is"); + break; + case FLT_COND_MARK: + NODE_NEW("mark", NULL); + ADD_ATTR("type", + FLT_IS_NOT_MATCH(cond->match_flag) + ? "is-not" : "is"); + break; + case FLT_COND_COLOR_LABEL: + NODE_NEW("color-label", NULL); + ADD_ATTR("type", + FLT_IS_NOT_MATCH(cond->match_flag) + ? "is-not" : "is"); + break; + case FLT_COND_MIME: + NODE_NEW("mime", NULL); + ADD_ATTR("type", + FLT_IS_NOT_MATCH(cond->match_flag) + ? "is-not" : "is"); + break; + case FLT_COND_ACCOUNT: + NODE_NEW("account-id", itos_buf(nstr, cond->int_value)); + break; + default: + break; + } + + if (node) { + fputs(" ", pfile->fp); + xml_file_put_node(pfile->fp, node); + xml_free_node(node); + } + } + + if (rule->target_folder) { + XMLNode *node; + + NODE_NEW("target-folder", rule->target_folder); + ADD_ATTR("recursive", rule->recursive + ? "true" : "false"); + fputs(" ", pfile->fp); + xml_file_put_node(pfile->fp, node); + xml_free_node(node); + } + + fputs(" \n", pfile->fp); + + fputs(" \n", pfile->fp); + + for (cur_action = rule->action_list; cur_action != NULL; + cur_action = cur_action->next) { + FilterAction *action = (FilterAction *)cur_action->data; + XMLNode *node = NULL; + + switch (action->type) { + case FLT_ACTION_MOVE: + NODE_NEW("move", action->str_value); + break; + case FLT_ACTION_COPY: + NODE_NEW("copy", action->str_value); + break; + case FLT_ACTION_NOT_RECEIVE: + NODE_NEW("not-receive", NULL); + break; + case FLT_ACTION_DELETE: + NODE_NEW("delete", NULL); + break; + case FLT_ACTION_EXEC: + NODE_NEW("exec", action->str_value); + break; + case FLT_ACTION_EXEC_ASYNC: + NODE_NEW("exec-async", action->str_value); + break; + case FLT_ACTION_MARK: + NODE_NEW("mark", NULL); + break; + case FLT_ACTION_COLOR_LABEL: + NODE_NEW("color-label", action->str_value); + break; + case FLT_ACTION_MARK_READ: + NODE_NEW("mark-as-read", NULL); + break; + case FLT_ACTION_FORWARD: + NODE_NEW("forward", action->str_value); + break; + case FLT_ACTION_FORWARD_AS_ATTACHMENT: + NODE_NEW("forward-as-attachment", + action->str_value); + break; + case FLT_ACTION_REDIRECT: + NODE_NEW("redirect", action->str_value); + break; + case FLT_ACTION_STOP_EVAL: + NODE_NEW("stop-eval", NULL); + break; + default: + break; + } + + if (node) { + fputs(" ", pfile->fp); + xml_file_put_node(pfile->fp, node); + xml_free_node(node); + } + } + + fputs(" \n", pfile->fp); + + fputs(" \n", pfile->fp); + } + + fputs("\n", pfile->fp); + + if (prefs_file_close(pfile) < 0) { + g_warning("failed to write filter configuration to file: %s\n", + file); + return; + } +} + +void filter_write_config(void) +{ + gchar *rcpath; + + debug_print("Writing filter configuration...\n"); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST, + NULL); + filter_write_file(prefs_common.fltlist, rcpath); + g_free(rcpath); +} + +#undef NODE_NEW +#undef ADD_ATTR + +gchar *filter_get_str(FilterRule *rule) +{ + gchar *str; + FilterCond *cond1, *cond2; + FilterAction *action = NULL; + GSList *cur; + gint flag1 = 0, flag2 = 0; + + cond1 = (FilterCond *)rule->cond_list->data; + if (rule->cond_list->next) + cond2 = (FilterCond *)rule->cond_list->next->data; + else + cond2 = NULL; + + /* new -> old flag conversion */ + switch (cond1->match_type) { + case FLT_CONTAIN: + case FLT_EQUAL: + flag1 = FLT_IS_NOT_MATCH(cond1->match_flag) ? 0 : FLT_O_CONTAIN; + if (FLT_IS_CASE_SENS(cond1->match_flag)) + flag1 |= FLT_O_CASE_SENS; + break; + case FLT_REGEX: + flag1 = FLT_O_REGEX; break; + default: + break; + } + if (cond2) { + switch (cond2->match_type) { + case FLT_CONTAIN: + case FLT_EQUAL: + flag2 = FLT_IS_NOT_MATCH(cond2->match_flag) ? 0 : FLT_O_CONTAIN; + if (FLT_IS_CASE_SENS(cond2->match_flag)) + flag2 |= FLT_O_CASE_SENS; + break; + case FLT_REGEX: + flag2 = FLT_O_REGEX; break; + default: + break; + } + } else + flag2 = FLT_O_CONTAIN; + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + if (action->type == FLT_ACTION_MOVE || + action->type == FLT_ACTION_NOT_RECEIVE || + action->type == FLT_ACTION_DELETE) + break; + } + + str = g_strdup_printf + ("%s:%s:%c:%s:%s:%s:%d:%d:%c", + cond1->header_name, cond1->str_value ? cond1->str_value : "", + (cond2 && cond2->header_name) ? + (rule->bool_op == FLT_AND ? '&' : '|') : ' ', + (cond2 && cond2->header_name) ? cond2->header_name : "", + (cond2 && cond2->str_value) ? cond2->str_value : "", + (action && action->str_value) ? action->str_value : "", + flag1, flag2, + (action && action->type == FLT_ACTION_MOVE) ? 'm' : + (action && action->type == FLT_ACTION_NOT_RECEIVE) ? 'n' : + (action && action->type == FLT_ACTION_DELETE) ? 'd' : ' '); + + return str; +} + +#define PARSE_ONE_PARAM(p, srcp) \ +{ \ + p = strchr(srcp, '\t'); \ + if (!p) return NULL; \ + else \ + *p++ = '\0'; \ +} + +FilterRule *filter_read_str(const gchar *str) +{ + FilterRule *rule; + FilterBoolOp bool_op; + gint flag; + FilterCond *cond; + FilterMatchType match_type; + FilterMatchFlag match_flag; + FilterAction *action; + GSList *cond_list = NULL; + GSList *action_list = NULL; + gchar *tmp; + gchar *rule_name; + gchar *name1, *body1, *op, *name2, *body2, *dest; + gchar *flag1 = NULL, *flag2 = NULL, *action1 = NULL; + + Xstrdup_a(tmp, str, return NULL); + + name1 = tmp; + PARSE_ONE_PARAM(body1, name1); + PARSE_ONE_PARAM(op, body1); + PARSE_ONE_PARAM(name2, op); + PARSE_ONE_PARAM(body2, name2); + PARSE_ONE_PARAM(dest, body2); + if (strchr(dest, '\t')) { + gchar *p; + + PARSE_ONE_PARAM(flag1, dest); + PARSE_ONE_PARAM(flag2, flag1); + PARSE_ONE_PARAM(action1, flag2); + if ((p = strchr(action1, '\t'))) *p = '\0'; + } + + bool_op = (*op == '&') ? FLT_AND : FLT_OR; + + if (*name1) { + if (flag1) + flag = (FilterOldFlag)strtoul(flag1, NULL, 10); + else + flag = FLT_O_CONTAIN; + match_type = FLT_CONTAIN; + match_flag = 0; + if (flag & FLT_O_REGEX) + match_type = FLT_REGEX; + else if (!(flag & FLT_O_CONTAIN)) + match_flag = FLT_NOT_MATCH; + if (flag & FLT_O_CASE_SENS) + match_flag |= FLT_CASE_SENS; + cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag, + name1, body1); + cond_list = g_slist_append(cond_list, cond); + } + if (*name2) { + if (flag2) + flag = (FilterOldFlag)strtoul(flag2, NULL, 10); + else + flag = FLT_O_CONTAIN; + match_type = FLT_CONTAIN; + match_flag = 0; + if (flag & FLT_O_REGEX) + match_type = FLT_REGEX; + else if (!(flag & FLT_O_CONTAIN)) + match_flag = FLT_NOT_MATCH; + if (flag & FLT_O_CASE_SENS) + match_flag |= FLT_CASE_SENS; + cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag, + name2, body2); + cond_list = g_slist_append(cond_list, cond); + } + + action = filter_action_new(FLT_ACTION_MOVE, + *dest ? g_strdup(dest) : NULL); + if (action1) { + switch (*action1) { + case 'm': action->type = FLT_ACTION_MOVE; break; + case 'n': action->type = FLT_ACTION_NOT_RECEIVE; break; + case 'd': action->type = FLT_ACTION_DELETE; break; + default: g_warning("Invalid action: `%c'\n", *action1); + } + } + action_list = g_slist_append(action_list, action); + + Xstrdup_a(rule_name, str, return NULL); + subst_char(rule_name, '\t', ':'); + + rule = filter_rule_new(rule_name, bool_op, cond_list, action_list); + + return rule; +} + +void filter_set_addressbook_func(FilterInAddressBookFunc func) +{ + default_addrbook_func = func; +} + +FilterInAddressBookFunc filter_get_addressbook_func(void) +{ + return default_addrbook_func; +} + +FilterRule *filter_rule_new(const gchar *name, FilterBoolOp bool_op, + GSList *cond_list, GSList *action_list) +{ + FilterRule *rule; + + rule = g_new0(FilterRule, 1); + rule->name = g_strdup(name); + rule->bool_op = bool_op; + rule->cond_list = cond_list; + rule->action_list = action_list; + rule->timing = FLT_TIMING_ANY; + rule->enabled = TRUE; + + return rule; +} + +FilterCond *filter_cond_new(FilterCondType type, + FilterMatchType match_type, + FilterMatchFlag match_flag, + const gchar *header, const gchar *value) +{ + FilterCond *cond; + + cond = g_new0(FilterCond, 1); + cond->type = type; + cond->match_type = match_type; + cond->match_flag = match_flag; + + if (type == FLT_COND_HEADER) + cond->header_name = + (header && *header) ? g_strdup(header) : NULL; + else + cond->header_name = NULL; + + cond->str_value = (value && *value) ? g_strdup(value) : NULL; + if (type == FLT_COND_SIZE_GREATER || type == FLT_COND_AGE_GREATER || + type == FLT_COND_ACCOUNT) + cond->int_value = atoi(value); + else + cond->int_value = 0; + + if (match_type == FLT_REGEX) + cond->match_func = strmatch_regex; + else if (match_type == FLT_EQUAL) { + if (FLT_IS_CASE_SENS(match_flag)) + cond->match_func = str_find_equal; + else + cond->match_func = str_case_find_equal; + } else if (match_type == FLT_IN_ADDRESSBOOK) { + cond->match_func = str_case_find_equal; + } else { + if (FLT_IS_CASE_SENS(match_flag)) + cond->match_func = str_find; + else + cond->match_func = str_case_find; + } + + return cond; +} + +FilterAction *filter_action_new(FilterActionType type, const gchar *str) +{ + FilterAction *action; + + action = g_new0(FilterAction, 1); + action->type = type; + + action->str_value = (str && *str) ? g_strdup(str) : NULL; + if (type == FLT_ACTION_COLOR_LABEL && str) + action->int_value = atoi(str); + else + action->int_value = 0; + + return action; +} + +FilterInfo *filter_info_new(void) +{ + FilterInfo *fltinfo; + + fltinfo = g_new0(FilterInfo, 1); + fltinfo->dest_list = NULL; + fltinfo->move_dest = NULL; + fltinfo->drop_done = FALSE; + fltinfo->error = FLT_ERROR_OK; + fltinfo->last_exec_exit_status = 0; + + return fltinfo; +} + +FilterRule *filter_junk_rule_create(PrefsAccount *account, + FolderItem *default_junk, + gboolean is_manual) +{ + FilterRule *rule; + FilterCond *cond; + FilterAction *action; + GSList *cond_list = NULL, *action_list = NULL; + gchar *junk_id = NULL; + FolderItem *item = NULL; + + if (!prefs_common.junk_classify_cmd) + return NULL; + + if (prefs_common.junk_folder) + item = folder_find_item_from_identifier(prefs_common.junk_folder); + + if (!item && account) { + Folder *folder = NULL; + GList *list; + + /* find most suitable Junk folder for account */ + + if (account->inbox && *account->inbox == '#') { + FolderItem *inbox; + inbox = folder_find_item_from_identifier(account->inbox); + if (inbox) { + folder = inbox->folder; + if (folder) + item = folder_get_junk(folder); + } + } + if (!item) { + folder = FOLDER(account->folder); + if (folder) + item = folder_get_junk(folder); + } + if (!item) { + for (list = folder_get_list(); list != NULL; list = list->next) { + folder = FOLDER(list->data); + if (FOLDER_IS_LOCAL(folder)) { + if (folder->account == account) + item = folder_get_junk(folder); + if (!item && folder->node) { + item = FOLDER_ITEM(folder->node->data); + if (item && item->account == account && item->folder) + item = folder_get_junk(item->folder); + else + item = NULL; + } + } + if (item) + break; + } + } + } + + if (!item) + item = default_junk; + if (!item) + item = folder_get_default_junk(); + if (!item) + return NULL; + junk_id = folder_item_get_identifier(item); + if (!junk_id) + return NULL; + + debug_print("filter_junk_rule_create: junk folder: %s\n", + junk_id); + + if (prefs_common.nofilter_junk_sender_in_book) { + cond = filter_cond_new(FLT_COND_HEADER, FLT_IN_ADDRESSBOOK, + FLT_NOT_MATCH, "From", NULL); + cond_list = g_slist_append(cond_list, cond); + } + + cond = filter_cond_new(FLT_COND_CMD_TEST, 0, 0, NULL, + prefs_common.junk_classify_cmd); + cond_list = g_slist_append(cond_list, cond); + + if (prefs_common.delete_junk_on_recv && !is_manual) { + action = filter_action_new(FLT_ACTION_COPY, junk_id); + action_list = g_slist_append(NULL, action); + action = filter_action_new(FLT_ACTION_DELETE, NULL); + action_list = g_slist_append(action_list, action); + } else { + action = filter_action_new(FLT_ACTION_MOVE, junk_id); + action_list = g_slist_append(NULL, action); + } + + if (prefs_common.mark_junk_as_read) { + action = filter_action_new(FLT_ACTION_MARK_READ, NULL); + action_list = g_slist_append(action_list, action); + } + + if (is_manual) + rule = filter_rule_new(_("Junk mail filter (manual)"), FLT_AND, + cond_list, action_list); + else + rule = filter_rule_new(_("Junk mail filter"), FLT_AND, + cond_list, action_list); + + g_free(junk_id); + + return rule; +} + +void filter_rule_rename_dest_path(FilterRule *rule, const gchar *old_path, + const gchar *new_path) +{ + FilterAction *action; + GSList *cur; + gchar *base; + gchar *dest_path; + gint oldpathlen; + + oldpathlen = strlen(old_path); + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + + if (action->type != FLT_ACTION_MOVE && + action->type != FLT_ACTION_COPY) + continue; + + if (action->str_value && + !strncmp(old_path, action->str_value, oldpathlen)) { + base = action->str_value + oldpathlen; + if (*base != '/' && *base != '\0') + continue; + while (*base == '/') base++; + if (*base == '\0') + dest_path = g_strdup(new_path); + else + dest_path = g_strconcat(new_path, "/", base, + NULL); + debug_print("filter_rule_rename_dest_path(): " + "renaming %s -> %s\n", + action->str_value, dest_path); + g_free(action->str_value); + action->str_value = dest_path; + } + } +} + +void filter_rule_delete_action_by_dest_path(FilterRule *rule, const gchar *path) +{ + FilterAction *action; + GSList *cur; + GSList *next; + gint pathlen; + + pathlen = strlen(path); + + for (cur = rule->action_list; cur != NULL; cur = next) { + action = (FilterAction *)cur->data; + next = cur->next; + + if (action->type != FLT_ACTION_MOVE && + action->type != FLT_ACTION_COPY) + continue; + + if (action->str_value && + !strncmp(path, action->str_value, pathlen) && + (action->str_value[pathlen] == '/' || + action->str_value[pathlen] == '\0')) { + debug_print("filter_rule_delete_action_by_dest_path(): " + "deleting %s\n", action->str_value); + rule->action_list = g_slist_remove + (rule->action_list, action); + filter_action_free(action); + } + } +} + +void filter_list_rename_path(const gchar *old_path, const gchar *new_path) +{ + GSList *cur; + + g_return_if_fail(old_path != NULL); + g_return_if_fail(new_path != NULL); + + for (cur = prefs_common.fltlist; cur != NULL; cur = cur->next) { + FilterRule *rule = (FilterRule *)cur->data; + filter_rule_rename_dest_path(rule, old_path, new_path); + } + + filter_write_config(); +} + +void filter_list_delete_path(const gchar *path) +{ + GSList *cur; + GSList *next; + + g_return_if_fail(path != NULL); + + for (cur = prefs_common.fltlist; cur != NULL; cur = next) { + FilterRule *rule = (FilterRule *)cur->data; + next = cur->next; + + filter_rule_delete_action_by_dest_path(rule, path); + if (!rule->action_list) { + prefs_common.fltlist = + g_slist_remove(prefs_common.fltlist, rule); + filter_rule_free(rule); + } + } + + filter_write_config(); +} + +void filter_rule_match_type_str_to_enum(const gchar *match_type, + FilterMatchType *type, + FilterMatchFlag *flag) +{ + g_return_if_fail(match_type != NULL); + + *type = FLT_CONTAIN; + *flag = 0; + + if (!strcmp(match_type, "contains")) { + *type = FLT_CONTAIN; + } else if (!strcmp(match_type, "not-contain")) { + *type = FLT_CONTAIN; + *flag = FLT_NOT_MATCH; + } else if (!strcmp(match_type, "is")) { + *type = FLT_EQUAL; + } else if (!strcmp(match_type, "is-not")) { + *type = FLT_EQUAL; + *flag = FLT_NOT_MATCH; + } else if (!strcmp(match_type, "regex")) { + *type = FLT_REGEX; + } else if (!strcmp(match_type, "not-regex")) { + *type = FLT_REGEX; + *flag = FLT_NOT_MATCH; + } else if (!strcmp(match_type, "in-addressbook")) { + *type = FLT_IN_ADDRESSBOOK; + } else if (!strcmp(match_type, "not-in-addressbook")) { + *type = FLT_IN_ADDRESSBOOK; + *flag = FLT_NOT_MATCH; + } else if (!strcmp(match_type, "gt")) { + } else if (!strcmp(match_type, "lt")) { + *flag = FLT_NOT_MATCH; + } +} + +void filter_get_keyword_from_msg(MsgInfo *msginfo, gchar **header, gchar **key, + FilterCreateType type) +{ + static HeaderEntry hentry[] = {{"List-Id:", NULL, TRUE}, + {"X-ML-Name:", NULL, TRUE}, + {"X-List:", NULL, TRUE}, + {"X-Mailing-list:", NULL, TRUE}, + {"X-Sequence:", NULL, TRUE}, + {NULL, NULL, FALSE}}; + enum + { + H_LIST_ID = 0, + H_X_ML_NAME = 1, + H_X_LIST = 2, + H_X_MAILING_LIST = 3, + H_X_SEQUENCE = 4 + }; + + FILE *fp; + + g_return_if_fail(msginfo != NULL); + g_return_if_fail(header != NULL); + g_return_if_fail(key != NULL); + + *header = NULL; + *key = NULL; + + switch (type) { + case FLT_BY_NONE: + return; + case FLT_BY_AUTO: + if ((fp = procmsg_open_message(msginfo)) == NULL) + return; + procheader_get_header_fields(fp, hentry); + fclose(fp); + +#define SET_FILTER_KEY(hstr, idx) \ +{ \ + *header = g_strdup(hstr); \ + *key = hentry[idx].body; \ + hentry[idx].body = NULL; \ +} + + if (hentry[H_LIST_ID].body != NULL) { + SET_FILTER_KEY("List-Id", H_LIST_ID); + extract_list_id_str(*key); + } else if (hentry[H_X_ML_NAME].body != NULL) { + SET_FILTER_KEY("X-ML-Name", H_X_ML_NAME); + } else if (hentry[H_X_LIST].body != NULL) { + SET_FILTER_KEY("X-List", H_X_LIST); + } else if (hentry[H_X_MAILING_LIST].body != NULL) { + SET_FILTER_KEY("X-Mailing-list", H_X_MAILING_LIST); + } else if (hentry[H_X_SEQUENCE].body != NULL) { + gchar *p; + + SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE); + p = *key; + while (*p != '\0') { + while (*p != '\0' && !g_ascii_isspace(*p)) p++; + while (g_ascii_isspace(*p)) p++; + if (g_ascii_isdigit(*p)) { + *p = '\0'; + break; + } + } + g_strstrip(*key); + } else if (msginfo->subject) { + *header = g_strdup("Subject"); + *key = g_strdup(msginfo->subject); + } + +#undef SET_FILTER_KEY + + g_free(hentry[H_LIST_ID].body); + hentry[H_LIST_ID].body = NULL; + g_free(hentry[H_X_ML_NAME].body); + hentry[H_X_ML_NAME].body = NULL; + g_free(hentry[H_X_LIST].body); + hentry[H_X_LIST].body = NULL; + g_free(hentry[H_X_MAILING_LIST].body); + hentry[H_X_MAILING_LIST].body = NULL; + + break; + case FLT_BY_FROM: + *header = g_strdup("From"); + *key = g_strdup(msginfo->from); + break; + case FLT_BY_TO: + *header = g_strdup("To"); + *key = g_strdup(msginfo->to); + break; + case FLT_BY_SUBJECT: + *header = g_strdup("Subject"); + *key = g_strdup(msginfo->subject); + break; + default: + break; + } +} + +void filter_rule_list_free(GSList *fltlist) +{ + GSList *cur; + + for (cur = fltlist; cur != NULL; cur = cur->next) + filter_rule_free((FilterRule *)cur->data); + g_slist_free(fltlist); +} + +void filter_rule_free(FilterRule *rule) +{ + if (!rule) return; + + g_free(rule->name); + g_free(rule->target_folder); + + filter_cond_list_free(rule->cond_list); + filter_action_list_free(rule->action_list); + + g_free(rule); +} + +void filter_cond_list_free(GSList *cond_list) +{ + GSList *cur; + + for (cur = cond_list; cur != NULL; cur = cur->next) + filter_cond_free((FilterCond *)cur->data); + g_slist_free(cond_list); +} + +void filter_action_list_free(GSList *action_list) +{ + GSList *cur; + + for (cur = action_list; cur != NULL; cur = cur->next) + filter_action_free((FilterAction *)cur->data); + g_slist_free(action_list); +} + +static void filter_cond_free(FilterCond *cond) +{ + g_free(cond->header_name); + g_free(cond->str_value); + g_free(cond); +} + +static void filter_action_free(FilterAction *action) +{ + g_free(action->str_value); + g_free(action); +} + +void filter_info_free(FilterInfo *fltinfo) +{ + g_slist_free(fltinfo->dest_list); + g_free(fltinfo); +} diff --git a/libsylph/filter.h b/libsylph/filter.h new file mode 100644 index 0000000..296c150 --- /dev/null +++ b/libsylph/filter.h @@ -0,0 +1,247 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2010 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __FILTER_H__ +#define __FILTER_H__ + +#include + +#include "folder.h" +#include "procmsg.h" +#include "utils.h" + +typedef struct _FilterCond FilterCond; +typedef struct _FilterAction FilterAction; +typedef struct _FilterRule FilterRule; +typedef struct _FilterInfo FilterInfo; + +typedef enum +{ + FLT_TIMING_ANY, + FLT_TIMING_ON_RECEIVE, + FLT_TIMING_MANUAL +} FilterTiming; + +typedef enum +{ + FLT_COND_HEADER, + FLT_COND_ANY_HEADER, + FLT_COND_TO_OR_CC, + FLT_COND_BODY, + FLT_COND_CMD_TEST, + FLT_COND_SIZE_GREATER, + FLT_COND_AGE_GREATER, + FLT_COND_UNREAD, + FLT_COND_MARK, + FLT_COND_COLOR_LABEL, + FLT_COND_MIME, + FLT_COND_ACCOUNT +} FilterCondType; + +typedef enum +{ + FLT_CONTAIN, + FLT_EQUAL, + FLT_REGEX, + FLT_IN_ADDRESSBOOK +} FilterMatchType; + +typedef enum +{ + FLT_NOT_MATCH = 1 << 0, + FLT_CASE_SENS = 1 << 1 +} FilterMatchFlag; + +typedef enum +{ + FLT_OR, + FLT_AND +} FilterBoolOp; + +typedef enum +{ + FLT_ACTION_MOVE, + FLT_ACTION_COPY, + FLT_ACTION_NOT_RECEIVE, + FLT_ACTION_DELETE, + FLT_ACTION_EXEC, + FLT_ACTION_EXEC_ASYNC, + FLT_ACTION_MARK, + FLT_ACTION_COLOR_LABEL, + FLT_ACTION_MARK_READ, + FLT_ACTION_FORWARD, + FLT_ACTION_FORWARD_AS_ATTACHMENT, + FLT_ACTION_REDIRECT, + FLT_ACTION_STOP_EVAL, + FLT_ACTION_NONE +} FilterActionType; + +typedef enum +{ + FLT_BY_NONE, + FLT_BY_AUTO, + FLT_BY_FROM, + FLT_BY_TO, + FLT_BY_SUBJECT +} FilterCreateType; + +typedef enum +{ + FLT_ERROR_OK, + FLT_ERROR_ERROR, + FLT_ERROR_EXEC_FAILED +} FilterErrorValue; + +#define FLT_IS_NOT_MATCH(flag) ((flag & FLT_NOT_MATCH) != 0) +#define FLT_IS_CASE_SENS(flag) ((flag & FLT_CASE_SENS) != 0) + +typedef gboolean (*FilterInAddressBookFunc) (const gchar *address); + +struct _FilterCond +{ + FilterCondType type; + + gchar *header_name; + + gchar *str_value; + gint int_value; + + FilterMatchType match_type; + FilterMatchFlag match_flag; + + StrFindFunc match_func; +}; + +struct _FilterAction +{ + FilterActionType type; + + gchar *str_value; + gint int_value; +}; + +struct _FilterRule +{ + gchar *name; + + FilterBoolOp bool_op; + + GSList *cond_list; + GSList *action_list; + + FilterTiming timing; + gboolean enabled; + + gchar *target_folder; + gboolean recursive; +}; + +struct _FilterInfo +{ + PrefsAccount *account; + MsgFlags flags; + + gboolean actions[FLT_ACTION_NONE]; + GSList *dest_list; + FolderItem *move_dest; + gboolean drop_done; + + FilterErrorValue error; + gint last_exec_exit_status; +}; + +gint filter_apply (GSList *fltlist, + const gchar *file, + FilterInfo *fltinfo); +gint filter_apply_msginfo (GSList *fltlist, + MsgInfo *msginfo, + FilterInfo *fltinfo); + +gint filter_action_exec (FilterRule *rule, + MsgInfo *msginfo, + const gchar *file, + FilterInfo *fltinfo); + +gboolean filter_match_rule (FilterRule *rule, + MsgInfo *msginfo, + GSList *hlist, + FilterInfo *fltinfo); + +gboolean filter_rule_requires_full_headers (FilterRule *rule); + +/* read / write config */ +GSList *filter_xml_node_to_filter_list (GNode *node); +GSList *filter_read_file (const gchar *file); +void filter_read_config (void); +void filter_write_file (GSList *list, + const gchar *file); +void filter_write_config (void); + +/* for old filterrc */ +gchar *filter_get_str (FilterRule *rule); +FilterRule *filter_read_str (const gchar *str); + +void filter_set_addressbook_func (FilterInAddressBookFunc func); +FilterInAddressBookFunc filter_get_addressbook_func + (void); + +FilterRule *filter_rule_new (const gchar *name, + FilterBoolOp bool_op, + GSList *cond_list, + GSList *action_list); +FilterCond *filter_cond_new (FilterCondType type, + FilterMatchType match_type, + FilterMatchFlag match_flag, + const gchar *header, + const gchar *value); +FilterAction *filter_action_new (FilterActionType type, + const gchar *str); +FilterInfo *filter_info_new (void); + +FilterRule *filter_junk_rule_create (PrefsAccount *account, + FolderItem *default_junk, + gboolean is_manual); + +void filter_rule_rename_dest_path (FilterRule *rule, + const gchar *old_path, + const gchar *new_path); +void filter_rule_delete_action_by_dest_path + (FilterRule *rule, + const gchar *path); + +void filter_list_rename_path (const gchar *old_path, + const gchar *new_path); +void filter_list_delete_path (const gchar *path); + +void filter_rule_match_type_str_to_enum (const gchar *type_str, + FilterMatchType *type, + FilterMatchFlag *flag); + +void filter_get_keyword_from_msg (MsgInfo *msginfo, + gchar **header, + gchar **key, + FilterCreateType type); + +void filter_rule_list_free (GSList *fltlist); +void filter_rule_free (FilterRule *rule); +void filter_cond_list_free (GSList *cond_list); +void filter_action_list_free (GSList *action_list); +void filter_info_free (FilterInfo *info); + +#endif /* __FILTER_H__ */ diff --git a/libsylph/folder.c b/libsylph/folder.c new file mode 100644 index 0000000..6fffe2d --- /dev/null +++ b/libsylph/folder.c @@ -0,0 +1,1994 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2012 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include + +#include "folder.h" +#include "session.h" +#include "imap.h" +#include "news.h" +#include "mh.h" +#include "virtual.h" +#include "utils.h" +#include "xml.h" +#include "codeconv.h" +#include "prefs.h" +#include "account.h" +#include "prefs_account.h" +#include "sylmain.h" + +typedef struct _FolderPrivData FolderPrivData; + +struct _FolderPrivData { + Folder *folder; + FolderItem *junk; + + FolderUIFunc2 ui_func2; + gpointer ui_func2_data; + + gpointer data; +}; + +static GList *folder_list = NULL; +static GList *folder_priv_list = NULL; + +static void folder_init (Folder *folder, + const gchar *name); + +static FolderPrivData *folder_get_priv (Folder *folder); + +static gboolean folder_read_folder_func (GNode *node, + gpointer data); +static gchar *folder_get_list_path (void); +static void folder_write_list_recursive (GNode *node, + gpointer data); + + +Folder *folder_new(FolderType type, const gchar *name, const gchar *path) +{ + Folder *folder = NULL; + + name = name ? name : path; + switch (type) { + case F_MH: + folder = mh_get_class()->folder_new(name, path); + break; + case F_IMAP: + folder = imap_get_class()->folder_new(name, path); + break; + case F_NEWS: + folder = news_get_class()->folder_new(name, path); + break; + default: + return NULL; + } + + return folder; +} + +static void folder_init(Folder *folder, const gchar *name) +{ + FolderItem *item; + + g_return_if_fail(folder != NULL); + + folder_set_name(folder, name); + folder->account = NULL; + folder->inbox = NULL; + folder->outbox = NULL; + folder->draft = NULL; + folder->queue = NULL; + folder->trash = NULL; + folder->ui_func = NULL; + folder->ui_func_data = NULL; + item = folder_item_new(name, NULL); + item->folder = folder; + folder->node = item->node = g_node_new(item); + folder->data = NULL; +} + +void folder_local_folder_init(Folder *folder, const gchar *name, + const gchar *path) +{ + folder_init(folder, name); + LOCAL_FOLDER(folder)->rootpath = g_strdup(path); +} + +void folder_remote_folder_init(Folder *folder, const gchar *name, + const gchar *path) +{ + folder_init(folder, name); + REMOTE_FOLDER(folder)->session = NULL; + REMOTE_FOLDER(folder)->remove_cache_on_destroy = TRUE; +} + +void folder_destroy(Folder *folder) +{ + FolderPrivData *priv; + + g_return_if_fail(folder != NULL); + g_return_if_fail(folder->klass->destroy != NULL); + + debug_print("folder_destroy: destroying Folder (%p)\n", folder); + + folder->klass->destroy(folder); + + folder_list = g_list_remove(folder_list, folder); + + folder_tree_destroy(folder); + + priv = folder_get_priv(folder); + folder_priv_list = g_list_remove(folder_priv_list, priv); + g_free(priv); + + g_free(folder->name); + g_free(folder); +} + +void folder_local_folder_destroy(LocalFolder *lfolder) +{ + g_return_if_fail(lfolder != NULL); + + g_free(lfolder->rootpath); +} + +void folder_remote_folder_destroy(RemoteFolder *rfolder) +{ + g_return_if_fail(rfolder != NULL); + + if (rfolder->session) + session_destroy(rfolder->session); +} + +gint folder_remote_folder_destroy_all_sessions(void) +{ + GList *list; + Folder *folder; + RemoteFolder *rfolder; + + for (list = folder_list; list != NULL; list = list->next) { + folder = FOLDER(list->data); + if (FOLDER_IS_REMOTE(folder)) { + rfolder = REMOTE_FOLDER(folder); + if (rfolder->session && + !folder_remote_folder_is_session_active(rfolder)) { + session_destroy(rfolder->session); + rfolder->session = NULL; + } + } + } + + return 0; +} + +gboolean folder_remote_folder_is_session_active(RemoteFolder *rfolder) +{ + g_return_val_if_fail(rfolder != NULL, FALSE); + + if (FOLDER_TYPE(rfolder) == F_IMAP) + return imap_is_session_active(IMAP_FOLDER(rfolder)); + + return FALSE; +} + +gboolean folder_remote_folder_active_session_exist(void) +{ + GList *list; + Folder *folder; + RemoteFolder *rfolder; + + for (list = folder_list; list != NULL; list = list->next) { + folder = FOLDER(list->data); + if (FOLDER_IS_REMOTE(folder)) { + rfolder = REMOTE_FOLDER(folder); + if (folder_remote_folder_is_session_active(rfolder)) + return TRUE; + } + } + + return FALSE; +} + +gint folder_scan_tree(Folder *folder) +{ + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(folder->klass->scan_tree != NULL, -1); + + return folder->klass->scan_tree(folder); +} + +gint folder_create_tree(Folder *folder) +{ + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(folder->klass->create_tree != NULL, -1); + + return folder->klass->create_tree(folder); +} + +FolderItem *folder_item_new(const gchar *name, const gchar *path) +{ + FolderItem *item; + + item = g_new0(FolderItem, 1); + + item->stype = F_NORMAL; + item->name = g_strdup(name); + item->path = g_strdup(path); + item->mtime = 0; + item->new = 0; + item->unread = 0; + item->total = 0; + item->unmarked_num = 0; + item->last_num = -1; + item->no_sub = FALSE; + item->no_select = FALSE; + item->collapsed = FALSE; + item->threaded = TRUE; + item->opened = FALSE; + item->updated = FALSE; + item->cache_dirty = FALSE; + item->mark_dirty = FALSE; + item->node = NULL; + item->parent = NULL; + item->folder = NULL; + item->account = NULL; + item->ac_apply_sub = FALSE; + item->auto_to = NULL; + item->use_auto_to_on_reply = FALSE; + item->auto_cc = NULL; + item->auto_bcc = NULL; + item->auto_replyto = NULL; + item->mark_queue = NULL; + item->last_selected = 0; + item->qsearch_cond_type = 0; + item->data = NULL; + + return item; +} + +void folder_item_append(FolderItem *parent, FolderItem *item) +{ + g_return_if_fail(parent != NULL); + g_return_if_fail(parent->folder != NULL); + g_return_if_fail(parent->node != NULL); + g_return_if_fail(item != NULL); + + item->parent = parent; + item->folder = parent->folder; + item->node = g_node_append_data(parent->node, item); +} + +FolderItem *folder_item_copy(FolderItem *item) +{ + FolderItem *new_item; + + new_item = g_new0(FolderItem, 1); + + new_item->stype = item->stype; + new_item->name = g_strdup(item->name); + new_item->path = g_strdup(item->path); + new_item->mtime = item->mtime; + new_item->new = item->new; + new_item->unread = item->unread; + new_item->total = item->total; + new_item->unmarked_num = item->unmarked_num; + new_item->last_num = item->last_num; + new_item->no_sub = item->no_sub; + new_item->no_select = item->no_select; + new_item->collapsed = item->collapsed; + new_item->threaded = item->threaded; + new_item->opened = item->opened; + new_item->updated = item->updated; + new_item->cache_dirty = item->cache_dirty; + new_item->mark_dirty = item->mark_dirty; + new_item->node = item->node; + new_item->parent = item->parent; + new_item->folder = item->folder; + new_item->account = item->account; + new_item->ac_apply_sub = item->ac_apply_sub; + new_item->auto_to = g_strdup(item->auto_to); + new_item->use_auto_to_on_reply = item->use_auto_to_on_reply; + new_item->auto_cc = g_strdup(item->auto_cc); + new_item->auto_bcc = g_strdup(item->auto_bcc); + new_item->auto_replyto = g_strdup(item->auto_replyto); + new_item->mark_queue = item->mark_queue; + new_item->last_selected = item->last_selected; + new_item->qsearch_cond_type = item->qsearch_cond_type; + new_item->data = item->data; + + return new_item; +} + +static gboolean folder_item_remove_func(GNode *node, gpointer data) +{ + FolderItem *item = FOLDER_ITEM(node->data); + + folder_item_destroy(item); + return FALSE; +} + +void folder_item_remove(FolderItem *item) +{ + GNode *node; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(item->node != NULL); + + node = item->node; + + if (item->folder->node == node) + item->folder->node = NULL; + + g_node_traverse(node, G_POST_ORDER, G_TRAVERSE_ALL, -1, + folder_item_remove_func, NULL); + g_node_destroy(node); +} + +void folder_item_remove_children(FolderItem *item) +{ + GNode *node, *next; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(item->node != NULL); + + node = item->node->children; + while (node != NULL) { + next = node->next; + folder_item_remove(FOLDER_ITEM(node->data)); + node = next; + } +} + +void folder_item_destroy(FolderItem *item) +{ + Folder *folder; + + g_return_if_fail(item != NULL); + + folder = item->folder; + if (folder) { + if (folder->inbox == item) + folder->inbox = NULL; + else if (folder->outbox == item) + folder->outbox = NULL; + else if (folder->draft == item) + folder->draft = NULL; + else if (folder->queue == item) + folder->queue = NULL; + else if (folder->trash == item) + folder->trash = NULL; + else if (folder_get_junk(folder) == item) + folder_set_junk(folder, NULL); + } + + g_free(item->name); + g_free(item->path); + g_free(item->auto_to); + g_free(item->auto_cc); + g_free(item->auto_bcc); + g_free(item->auto_replyto); + g_free(item); +} + +gint folder_item_compare(FolderItem *item_a, FolderItem *item_b) +{ + gint ret; + gchar *str_a, *str_b; + + if (!item_a || !item_b) + return 0; + if (!item_a->parent || !item_b->parent) + return 0; + if (!item_a->name || !item_b->name) + 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. As a special + * case, two virtual folders are compared like normal ones. */ + if (item_a->stype != F_NORMAL && item_b->stype != F_NORMAL && + (item_a->stype != F_VIRTUAL || item_b->stype != F_VIRTUAL)) + 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; + + /* otherwise just compare the folder names */ + str_a = g_utf8_casefold(item_a->name, -1); + str_b = g_utf8_casefold(item_b->name, -1); + ret = g_utf8_collate(str_a, str_b); + g_free(str_b); + g_free(str_a); + + return ret; +} + +void folder_set_ui_func(Folder *folder, FolderUIFunc func, gpointer data) +{ + g_return_if_fail(folder != NULL); + + folder->ui_func = func; + folder->ui_func_data = data; +} + +void folder_set_ui_func2(Folder *folder, FolderUIFunc2 func, gpointer data) +{ + FolderPrivData *priv; + + priv = folder_get_priv(folder); + if (priv) { + priv->ui_func2 = func; + priv->ui_func2_data = data; + } +} + +FolderUIFunc2 folder_get_ui_func2(Folder *folder) +{ + FolderPrivData *priv; + + priv = folder_get_priv(folder); + if (priv) + return priv->ui_func2; + + return NULL; +} + +gboolean folder_call_ui_func2(Folder *folder, FolderItem *item, guint count, + guint total) +{ + FolderPrivData *priv; + + priv = folder_get_priv(folder); + if (priv && priv->ui_func2) { + return priv->ui_func2(folder, item, count, total, + priv->ui_func2_data); + } + + return TRUE; +} + +void folder_set_name(Folder *folder, const gchar *name) +{ + g_return_if_fail(folder != NULL); + + g_free(folder->name); + folder->name = name ? g_strdup(name) : NULL; + if (folder->node && folder->node->data) { + FolderItem *item = (FolderItem *)folder->node->data; + + g_free(item->name); + item->name = name ? g_strdup(name) : NULL; + } +} + +void folder_tree_destroy(Folder *folder) +{ + g_return_if_fail(folder != NULL); + + if (folder->node) + folder_item_remove(FOLDER_ITEM(folder->node->data)); +} + +void folder_add(Folder *folder) +{ + Folder *cur_folder; + GList *cur; + gint i; + FolderPrivData *priv; + + debug_print("Adding Folder (%p) to folder list\n", folder); + + g_return_if_fail(folder != NULL); + + for (i = 0, cur = folder_list; cur != NULL; cur = cur->next, i++) { + cur_folder = FOLDER(cur->data); + if (FOLDER_TYPE(folder) == F_MH) { + if (FOLDER_TYPE(cur_folder) != F_MH) break; + } else if (FOLDER_TYPE(folder) == F_IMAP) { + if (FOLDER_TYPE(cur_folder) != F_MH && + FOLDER_TYPE(cur_folder) != F_IMAP) break; + } else if (FOLDER_TYPE(folder) == F_NEWS) { + if (FOLDER_TYPE(cur_folder) != F_MH && + FOLDER_TYPE(cur_folder) != F_IMAP && + FOLDER_TYPE(cur_folder) != F_NEWS) break; + } + } + + folder_list = g_list_insert(folder_list, folder, i); + priv = g_new0(FolderPrivData, 1); + priv->folder = folder; + folder_priv_list = g_list_insert(folder_priv_list, priv, i); +} + +GList *folder_get_list(void) +{ + return folder_list; +} + +gint folder_read_list(void) +{ + GNode *node; + XMLNode *xmlnode; + gchar *path; + + path = folder_get_list_path(); + if (!is_file_exist(path)) return -1; + node = xml_parse_file(path); + if (!node) return -1; + + xmlnode = node->data; + if (strcmp2(xmlnode->tag->tag, "folderlist") != 0) { + g_warning("wrong folder list\n"); + xml_free_tree(node); + return -1; + } + + g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2, + folder_read_folder_func, NULL); + + xml_free_tree(node); + if (folder_list) + return 0; + else + return -1; +} + +void folder_write_list(void) +{ + GList *list; + Folder *folder; + gchar *path; + PrefFile *pfile; + + path = folder_get_list_path(); + if ((pfile = prefs_file_open(path)) == NULL) return; + + fprintf(pfile->fp, "\n", + CS_INTERNAL); + fputs("\n\n", pfile->fp); + + for (list = folder_list; list != NULL; list = list->next) { + folder = list->data; + folder_write_list_recursive(folder->node, pfile->fp); + } + + fputs("\n", pfile->fp); + + if (prefs_file_close(pfile) < 0) + g_warning("failed to write folder list.\n"); + + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "folderlist-updated"); +} + +struct TotalMsgStatus +{ + guint new; + guint unread; + guint total; + GString *str; +}; + +static gboolean folder_get_status_full_all_func(GNode *node, gpointer data) +{ + FolderItem *item; + struct TotalMsgStatus *status = (struct TotalMsgStatus *)data; + gchar *id; + + g_return_val_if_fail(node->data != NULL, FALSE); + + item = FOLDER_ITEM(node->data); + + if (!item->path) return FALSE; + + status->new += item->new; + status->unread += item->unread; + status->total += item->total; + + if (status->str) { + id = folder_item_get_identifier(item); + g_string_sprintfa(status->str, "%5d %5d %5d %s\n", + item->new, item->unread, + item->total, id); + g_free(id); + } + + return FALSE; +} + +static void folder_get_status_full_all(GString *str, guint *new, guint *unread, + guint *total) +{ + GList *list; + Folder *folder; + struct TotalMsgStatus status; + + status.new = status.unread = status.total = 0; + status.str = str; + + debug_print("Counting total number of messages...\n"); + + for (list = folder_list; list != NULL; list = list->next) { + folder = FOLDER(list->data); + if (folder->node) + g_node_traverse(folder->node, G_PRE_ORDER, + G_TRAVERSE_ALL, -1, + folder_get_status_full_all_func, + &status); + } + + *new = status.new; + *unread = status.unread; + *total = status.total; +} + +gchar *folder_get_status(GPtrArray *folders, gboolean full) +{ + guint new, unread, total; + GString *str; + gint i; + gchar *ret; + + new = unread = total = 0; + + str = g_string_new(NULL); + + if (folders) { + for (i = 0; i < folders->len; i++) { + FolderItem *item; + + item = g_ptr_array_index(folders, i); + new += item->new; + unread += item->unread; + total += item->total; + + if (full) { + gchar *id; + + id = folder_item_get_identifier(item); + g_string_sprintfa(str, "%5d %5d %5d %s\n", + item->new, item->unread, + item->total, id); + g_free(id); + } + } + } else { + folder_get_status_full_all(full ? str : NULL, + &new, &unread, &total); + } + + if (full) + g_string_sprintfa(str, "%5d %5d %5d\n", new, unread, total); + else + g_string_sprintfa(str, "%d %d %d\n", new, unread, total); + + ret = str->str; + g_string_free(str, FALSE); + + return ret; +} + +Folder *folder_find_from_path(const gchar *path) +{ + GList *list; + Folder *folder; + + for (list = folder_list; list != NULL; list = list->next) { + folder = list->data; + if (FOLDER_TYPE(folder) == F_MH && + !path_cmp(LOCAL_FOLDER(folder)->rootpath, path)) + return folder; + } + + return NULL; +} + +Folder *folder_find_from_name(const gchar *name, FolderType type) +{ + GList *list; + Folder *folder; + + for (list = folder_list; list != NULL; list = list->next) { + folder = list->data; + if (FOLDER_TYPE(folder) == type && + strcmp2(name, folder->name) == 0) + return folder; + } + + return NULL; +} + +static gboolean folder_item_find_func(GNode *node, gpointer data) +{ + FolderItem *item = node->data; + gpointer *d = data; + const gchar *path = d[0]; + + if (path_cmp(path, item->path) != 0) + return FALSE; + + d[1] = item; + + return TRUE; +} + +FolderItem *folder_find_item_from_path(const gchar *path) +{ + Folder *folder; + gpointer d[2]; + + folder = folder_get_default_folder(); + g_return_val_if_fail(folder != NULL, NULL); + + d[0] = (gpointer)path; + d[1] = NULL; + g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + folder_item_find_func, d); + return d[1]; +} + +FolderItem *folder_find_child_item_by_name(FolderItem *item, const gchar *name) +{ + GNode *node; + FolderItem *child; + const gchar *base; + + if (!name) + return NULL; + + for (node = item->node->children; node != NULL; node = node->next) { + child = FOLDER_ITEM(node->data); + base = g_basename(child->path); +#ifdef G_OS_WIN32 + if (base && g_ascii_strcasecmp(base, name) == 0) +#else + if (strcmp2(base, name) == 0) +#endif + return child; + } + + return NULL; +} + +static const struct { + gchar *str; + FolderType type; +} type_str_table[] = { + {"#mh" , F_MH}, + {"#mbox" , F_MBOX}, + {"#maildir", F_MAILDIR}, + {"#imap" , F_IMAP}, + {"#news" , F_NEWS} +}; + +static gchar *folder_get_type_string(FolderType type) +{ + gint i; + + for (i = 0; i < sizeof(type_str_table) / sizeof(type_str_table[0]); + i++) { + if (type_str_table[i].type == type) + return type_str_table[i].str; + } + + return NULL; +} + +static FolderType folder_get_type_from_string(const gchar *str) +{ + gint i; + + for (i = 0; i < sizeof(type_str_table) / sizeof(type_str_table[0]); + i++) { + if (g_ascii_strcasecmp(type_str_table[i].str, str) == 0) + return type_str_table[i].type; + } + + return F_UNKNOWN; +} + +gchar *folder_get_identifier(Folder *folder) +{ + gchar *type_str; + + g_return_val_if_fail(folder != NULL, NULL); + + type_str = folder_get_type_string(FOLDER_TYPE(folder)); + return g_strconcat(type_str, "/", folder->name, NULL); +} + +gchar *folder_item_get_identifier(FolderItem *item) +{ + gchar *id; + gchar *folder_id; + + g_return_val_if_fail(item != NULL, NULL); + + if (!item->path) { + if (!item->parent) + return folder_get_identifier(item->folder); + else + return NULL; + } + + folder_id = folder_get_identifier(item->folder); + id = g_strconcat(folder_id, "/", item->path, NULL); + g_free(folder_id); + + return id; +} + +FolderItem *folder_find_item_from_identifier(const gchar *identifier) +{ + Folder *folder; + gpointer d[2]; + gchar *str; + gchar *p; + gchar *name; + gchar *path; + FolderType type; + + g_return_val_if_fail(identifier != NULL, NULL); + + if (*identifier != '#') + return folder_find_item_from_path(identifier); + + Xstrdup_a(str, identifier, return NULL); + + p = strchr(str, '/'); + if (!p) + return folder_find_item_from_path(identifier); + *p = '\0'; + p++; + type = folder_get_type_from_string(str); + if (type == F_UNKNOWN) + return folder_find_item_from_path(identifier); + + name = p; + p = strchr(p, '/'); + if (p) { + *p = '\0'; + p++; + } + + folder = folder_find_from_name(name, type); + if (!folder) + return folder_find_item_from_path(identifier); + + if (!p) + return FOLDER_ITEM(folder->node->data); + + path = p; + + d[0] = (gpointer)path; + d[1] = NULL; + g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + folder_item_find_func, d); + return d[1]; +} + +FolderItem *folder_find_item_and_num_from_id(const gchar *identifier, gint *num) +{ + gchar *id; + gchar *msg; + FolderItem *item; + + g_return_val_if_fail(identifier != NULL, NULL); + + id = g_path_get_dirname(identifier); + msg = g_path_get_basename(identifier); + item = folder_find_item_from_identifier(id); + *num = to_number(msg); + g_free(msg); + g_free(id); + + return item; +} + +Folder *folder_get_default_folder(void) +{ + return folder_list ? FOLDER(folder_list->data) : NULL; +} + +FolderItem *folder_get_default_inbox(void) +{ + Folder *folder; + + if (!folder_list) return NULL; + folder = FOLDER(folder_list->data); + g_return_val_if_fail(folder != NULL, NULL); + return folder->inbox; +} + +FolderItem *folder_get_default_outbox(void) +{ + Folder *folder; + + if (!folder_list) return NULL; + folder = FOLDER(folder_list->data); + g_return_val_if_fail(folder != NULL, NULL); + return folder->outbox; +} + +FolderItem *folder_get_default_draft(void) +{ + Folder *folder; + + if (!folder_list) return NULL; + folder = FOLDER(folder_list->data); + g_return_val_if_fail(folder != NULL, NULL); + return folder->draft; +} + +FolderItem *folder_get_default_queue(void) +{ + Folder *folder; + + if (!folder_list) return NULL; + folder = FOLDER(folder_list->data); + g_return_val_if_fail(folder != NULL, NULL); + return folder->queue; +} + +FolderItem *folder_get_default_trash(void) +{ + Folder *folder; + + if (!folder_list) return NULL; + folder = FOLDER(folder_list->data); + g_return_val_if_fail(folder != NULL, NULL); + return folder->trash; +} + +FolderItem *folder_get_default_junk(void) +{ + FolderPrivData *priv; + + if (!folder_list) return NULL; + if (!folder_priv_list) return NULL; + priv = (FolderPrivData *)folder_priv_list->data; + g_return_val_if_fail(priv != NULL, NULL); + g_return_val_if_fail(priv->folder != NULL, NULL); + return priv->junk; +} + +static FolderPrivData *folder_get_priv(Folder *folder) +{ + FolderPrivData *priv; + GList *cur; + + g_return_val_if_fail(folder != NULL, NULL); + + for (cur = folder_priv_list; cur != NULL; cur = cur->next) { + priv = (FolderPrivData *)cur->data; + if (priv->folder == folder) + return priv; + } + + g_warning("folder_get_priv: private data for Folder (%p) not found.", + folder); + + return NULL; +} + +FolderItem *folder_get_junk(Folder *folder) +{ + FolderPrivData *priv; + + priv = folder_get_priv(folder); + if (priv) + return priv->junk; + + return NULL; +} + +void folder_set_junk(Folder *folder, FolderItem *item) +{ + FolderPrivData *priv; + + priv = folder_get_priv(folder); + if (priv) + priv->junk = item; +} + +gboolean folder_item_is_trash(FolderItem *item) +{ + PrefsAccount *ac; + FolderItem *trash; + + g_return_val_if_fail(item != NULL, FALSE); + + if (item->stype == F_TRASH) + return TRUE; + + ac = account_find_from_item_property(item); + if (ac && ac->set_trash_folder && ac->trash_folder) { + trash = folder_find_item_from_identifier(ac->trash_folder); + if (trash == item) + return TRUE; + } + + return FALSE; +} + +#define CREATE_FOLDER_IF_NOT_EXIST(member, dir, type) \ +{ \ + if (!folder->member) { \ + item = folder_item_new(dir, dir); \ + item->stype = type; \ + folder_item_append(rootitem, item); \ + folder->member = item; \ + } \ +} + +void folder_set_missing_folders(void) +{ + Folder *folder; + FolderItem *rootitem; + FolderItem *item; + GList *list; + + for (list = folder_list; list != NULL; list = list->next) { + folder = list->data; + if (FOLDER_TYPE(folder) != F_MH) continue; + rootitem = FOLDER_ITEM(folder->node->data); + g_return_if_fail(rootitem != NULL); + + if (folder->inbox && folder->outbox && folder->draft && + folder->queue && folder->trash && folder_get_junk(folder)) + continue; + + if (folder_create_tree(folder) < 0) { + g_warning("%s: can't create the folder tree.\n", + LOCAL_FOLDER(folder)->rootpath); + continue; + } + + CREATE_FOLDER_IF_NOT_EXIST(inbox, INBOX_DIR, F_INBOX); + CREATE_FOLDER_IF_NOT_EXIST(outbox, OUTBOX_DIR, F_OUTBOX); + CREATE_FOLDER_IF_NOT_EXIST(draft, DRAFT_DIR, F_DRAFT); + CREATE_FOLDER_IF_NOT_EXIST(queue, QUEUE_DIR, F_QUEUE); + CREATE_FOLDER_IF_NOT_EXIST(trash, TRASH_DIR, F_TRASH); + + if (!folder_get_junk(folder)) { + item = folder_item_new(JUNK_DIR, JUNK_DIR); + item->stype = F_JUNK; + folder_item_append(rootitem, item); + folder_set_junk(folder, item); + } + } +} + +static gboolean folder_unref_account_func(GNode *node, gpointer data) +{ + FolderItem *item = node->data; + PrefsAccount *account = data; + + if (item->account == account) + item->account = NULL; + + return FALSE; +} + +void folder_unref_account_all(PrefsAccount *account) +{ + Folder *folder; + GList *list; + + if (!account) return; + + for (list = folder_list; list != NULL; list = list->next) { + folder = list->data; + if (folder->account == account) + folder->account = NULL; + g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + folder_unref_account_func, account); + } +} + +#undef CREATE_FOLDER_IF_NOT_EXIST + +gchar *folder_get_path(Folder *folder) +{ + gchar *path; + + g_return_val_if_fail(folder != NULL, NULL); + + if (FOLDER_TYPE(folder) == F_MH) { + path = g_filename_from_utf8(LOCAL_FOLDER(folder)->rootpath, + -1, NULL, NULL, NULL); + if (!path) { + g_warning("folder_get_path: failed to convert character set\n"); + path = g_strdup(LOCAL_FOLDER(folder)->rootpath); + } + if (!g_path_is_absolute(path)) { + gchar *path_; + + path_ = g_strconcat(get_mail_base_dir(), + G_DIR_SEPARATOR_S, path, NULL); + g_free(path); + path = path_; + } + } else if (FOLDER_TYPE(folder) == F_IMAP) { + gchar *server; + gchar *uid; + + g_return_val_if_fail(folder->account != NULL, NULL); + server = uriencode_for_filename(folder->account->recv_server); + uid = uriencode_for_filename(folder->account->userid); + path = g_strconcat(get_imap_cache_dir(), + G_DIR_SEPARATOR_S, server, + G_DIR_SEPARATOR_S, uid, NULL); + g_free(uid); + g_free(server); + } else if (FOLDER_TYPE(folder) == F_NEWS) { + gchar *server; + + g_return_val_if_fail(folder->account != NULL, NULL); + server = uriencode_for_filename(folder->account->nntp_server); + path = g_strconcat(get_news_cache_dir(), + G_DIR_SEPARATOR_S, server, NULL); + g_free(server); + } else + path = NULL; + + return path; +} + +gchar *folder_item_get_path(FolderItem *item) +{ + gchar *folder_path; + gchar *item_path = NULL, *path; + + g_return_val_if_fail(item != NULL, NULL); + + folder_path = folder_get_path(item->folder); + g_return_val_if_fail(folder_path != NULL, NULL); + + if (item->path) { + item_path = g_filename_from_utf8(item->path, -1, + NULL, NULL, NULL); + if (!item_path) { + g_warning("folder_item_get_path: failed to convert character set\n"); + item_path = g_strdup(item->path); + } +#ifdef G_OS_WIN32 + subst_char(item_path, '/', G_DIR_SEPARATOR); +#endif + } + + if (item_path) + path = g_strconcat(folder_path, G_DIR_SEPARATOR_S, item_path, + NULL); + else + path = g_strdup(folder_path); + + g_free(item_path); + g_free(folder_path); + return path; +} + +gint folder_item_scan(FolderItem *item) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, -1); + + folder = item->folder; + return folder->klass->scan(folder, item); +} + +static void folder_item_scan_foreach_func(gpointer key, gpointer val, + gpointer data) +{ + folder_item_scan(FOLDER_ITEM(key)); +} + +void folder_item_scan_foreach(GHashTable *table) +{ + g_hash_table_foreach(table, folder_item_scan_foreach_func, NULL); +} + +GSList *folder_item_get_msg_list(FolderItem *item, gboolean use_cache) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, NULL); + + folder = item->folder; + + if (item->stype == F_VIRTUAL) + return virtual_get_class()->get_msg_list(folder, item, + use_cache); + + return folder->klass->get_msg_list(folder, item, use_cache); +} + +GSList *folder_item_get_uncached_msg_list(FolderItem *item) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->folder->klass->get_uncached_msg_list != NULL, + NULL); + + folder = item->folder; + + if (item->stype == F_VIRTUAL) + return NULL; + + return folder->klass->get_uncached_msg_list(folder, item); +} + +gchar *folder_item_fetch_msg(FolderItem *item, gint num) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, NULL); + + folder = item->folder; + + return folder->klass->fetch_msg(folder, item, num); +} + +gint folder_item_fetch_all_msg(FolderItem *item) +{ + Folder *folder; + GSList *mlist; + GSList *cur; + gint num = 0; + gint ret = 0; + + g_return_val_if_fail(item != NULL, -1); + + debug_print("fetching all messages in %s ...\n", item->path); + + folder = item->folder; + + if (folder->ui_func) + folder->ui_func(folder, item, folder->ui_func_data ? + folder->ui_func_data : GINT_TO_POINTER(num)); + + mlist = folder_item_get_msg_list(item, TRUE); + + for (cur = mlist; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + gchar *msg; + + num++; + if (folder->ui_func) + folder->ui_func(folder, item, + folder->ui_func_data ? + folder->ui_func_data : + GINT_TO_POINTER(num)); + + msg = folder_item_fetch_msg(item, msginfo->msgnum); + if (!msg) { + g_warning("Can't fetch message %d. Aborting.\n", + msginfo->msgnum); + ret = -1; + break; + } + g_free(msg); + } + + procmsg_msg_list_free(mlist); + + return ret; +} + +MsgInfo *folder_item_get_msginfo(FolderItem *item, gint num) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, NULL); + + folder = item->folder; + + return folder->klass->get_msginfo(folder, item, num); +} + +gint folder_item_add_msg(FolderItem *dest, const gchar *file, MsgFlags *flags, + gboolean remove_source) +{ + Folder *folder; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(file != NULL, -1); + g_return_val_if_fail(dest->folder->klass->add_msg != NULL, -1); + + folder = dest->folder; + + return folder->klass->add_msg(folder, dest, file, flags, remove_source); +} + +gint folder_item_add_msgs(FolderItem *dest, GSList *file_list, + gboolean remove_source, gint *first) +{ + Folder *folder; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(file_list != NULL, -1); + g_return_val_if_fail(dest->folder->klass->add_msgs != NULL, -1); + + folder = dest->folder; + + return folder->klass->add_msgs(folder, dest, file_list, remove_source, + first); +} + +gint folder_item_add_msg_msginfo(FolderItem *dest, MsgInfo *msginfo, + gboolean remove_source) +{ + Folder *folder; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(msginfo->file_path != NULL, -1); + g_return_val_if_fail(dest->folder->klass->add_msg_msginfo != NULL, -1); + + folder = dest->folder; + + return folder->klass->add_msg_msginfo(folder, dest, msginfo, + remove_source); +} + +gint folder_item_add_msgs_msginfo(FolderItem *dest, GSList *msglist, + gboolean remove_source, gint *first) +{ + Folder *folder; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + g_return_val_if_fail(dest->folder->klass->add_msgs_msginfo != NULL, -1); + + folder = dest->folder; + + return folder->klass->add_msgs_msginfo(folder, dest, msglist, + remove_source, first); +} + +#define IS_FROM_QUEUE(m, d) \ + (m->folder && m->folder->stype == F_QUEUE && \ + MSG_IS_QUEUED(m->flags) && d->stype != F_QUEUE) + +gint folder_item_move_msg(FolderItem *dest, MsgInfo *msginfo) +{ + Folder *folder; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(dest->folder->klass->move_msg != NULL, -1); + + folder = dest->folder; + + if (IS_FROM_QUEUE(msginfo, dest)) { + GSList msglist; + + msglist.data = msginfo; + msglist.next = NULL; + return procmsg_add_messages_from_queue(dest, &msglist, TRUE); + } + + return folder->klass->move_msg(folder, dest, msginfo); +} + +gint folder_item_move_msgs(FolderItem *dest, GSList *msglist) +{ + Folder *folder; + MsgInfo *msginfo; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + g_return_val_if_fail(dest->folder->klass->move_msgs != NULL, -1); + + folder = dest->folder; + + msginfo = (MsgInfo *)msglist->data; + if (IS_FROM_QUEUE(msginfo, dest)) + return procmsg_add_messages_from_queue(dest, msglist, TRUE); + + return folder->klass->move_msgs(folder, dest, msglist); +} + +gint folder_item_copy_msg(FolderItem *dest, MsgInfo *msginfo) +{ + Folder *folder; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(dest->folder->klass->copy_msg != NULL, -1); + + folder = dest->folder; + + if (IS_FROM_QUEUE(msginfo, dest)) { + GSList msglist; + + msglist.data = msginfo; + msglist.next = NULL; + return procmsg_add_messages_from_queue(dest, &msglist, FALSE); + } + + return folder->klass->copy_msg(folder, dest, msginfo); +} + +gint folder_item_copy_msgs(FolderItem *dest, GSList *msglist) +{ + Folder *folder; + MsgInfo *msginfo; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + g_return_val_if_fail(dest->folder->klass->copy_msgs != NULL, -1); + + folder = dest->folder; + + msginfo = (MsgInfo *)msglist->data; + if (IS_FROM_QUEUE(msginfo, dest)) + return procmsg_add_messages_from_queue(dest, msglist, FALSE); + + return folder->klass->copy_msgs(folder, dest, msglist); +} + +#undef IS_FROM_QUEUE + +gint folder_item_remove_msg(FolderItem *item, MsgInfo *msginfo) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->folder->klass->remove_msg != NULL, -1); + + folder = item->folder; + + return folder->klass->remove_msg(folder, item, msginfo); +} + +gint folder_item_remove_msgs(FolderItem *item, GSList *msglist) +{ + Folder *folder; + gint ret = 0; + + g_return_val_if_fail(item != NULL, -1); + + folder = item->folder; + if (folder->klass->remove_msgs) { + return folder->klass->remove_msgs(folder, item, msglist); + } + + while (msglist != NULL) { + MsgInfo *msginfo = (MsgInfo *)msglist->data; + + ret = folder_item_remove_msg(item, msginfo); + if (ret != 0) break; + msglist = msglist->next; + } + + return ret; +} + +gint folder_item_remove_all_msg(FolderItem *item) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->folder->klass->remove_all_msg != NULL, -1); + + folder = item->folder; + + return folder->klass->remove_all_msg(folder, item); +} + +gboolean folder_item_is_msg_changed(FolderItem *item, MsgInfo *msginfo) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, FALSE); + g_return_val_if_fail(item->folder->klass->is_msg_changed != NULL, + FALSE); + + folder = item->folder; + return folder->klass->is_msg_changed(folder, item, msginfo); +} + +gint folder_item_close(FolderItem *item) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, -1); + + item->opened = FALSE; + folder = item->folder; + return folder->klass->close(folder, item); +} + +gchar *folder_item_get_cache_file(FolderItem *item) +{ + gchar *path; + gchar *file; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->path != NULL, NULL); + + path = folder_item_get_path(item); + g_return_val_if_fail(path != NULL, NULL); + if (!is_dir_exist(path)) + make_dir_hier(path); + file = g_strconcat(path, G_DIR_SEPARATOR_S, CACHE_FILE, NULL); + g_free(path); + + return file; +} + +gchar *folder_item_get_mark_file(FolderItem *item) +{ + gchar *path; + gchar *file; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->path != NULL, NULL); + + path = folder_item_get_path(item); + g_return_val_if_fail(path != NULL, NULL); + if (!is_dir_exist(path)) + make_dir_hier(path); + file = g_strconcat(path, G_DIR_SEPARATOR_S, MARK_FILE, NULL); + g_free(path); + + return file; +} + +static gboolean folder_build_tree(GNode *node, gpointer data) +{ + Folder *folder = FOLDER(data); + FolderItem *item; + XMLNode *xmlnode; + GList *list; + SpecialFolderItemType stype = F_NORMAL; + const gchar *name = NULL; + const gchar *path = NULL; + PrefsAccount *account = NULL; + gboolean no_sub = FALSE, no_select = FALSE, collapsed = FALSE, + threaded = TRUE, ac_apply_sub = FALSE; + FolderSortKey sort_key = SORT_BY_NONE; + FolderSortType sort_type = SORT_ASCENDING; + gboolean qsearch_cond_type = 0; + gint new = 0, unread = 0, total = 0; + time_t mtime = 0; + gboolean use_auto_to_on_reply = FALSE; + gchar *auto_to = NULL, *auto_cc = NULL, *auto_bcc = NULL, + *auto_replyto = NULL; + gboolean trim_summary_subject = FALSE, trim_compose_subject = FALSE; + + g_return_val_if_fail(node->data != NULL, FALSE); + if (!node->parent) return FALSE; + + xmlnode = node->data; + if (strcmp2(xmlnode->tag->tag, "folderitem") != 0) { + g_warning("tag name != \"folderitem\"\n"); + return FALSE; + } + + list = xmlnode->tag->attr; + for (; list != NULL; list = list->next) { + XMLAttr *attr = list->data; + + if (!attr || !attr->name || !attr->value) continue; + if (!strcmp(attr->name, "type")) { + if (!g_ascii_strcasecmp(attr->value, "normal")) + stype = F_NORMAL; + else if (!g_ascii_strcasecmp(attr->value, "inbox")) + stype = F_INBOX; + else if (!g_ascii_strcasecmp(attr->value, "outbox")) + stype = F_OUTBOX; + else if (!g_ascii_strcasecmp(attr->value, "draft")) + stype = F_DRAFT; + else if (!g_ascii_strcasecmp(attr->value, "queue")) + stype = F_QUEUE; + else if (!g_ascii_strcasecmp(attr->value, "trash")) + stype = F_TRASH; + else if (!g_ascii_strcasecmp(attr->value, "junk")) + stype = F_JUNK; + else if (!g_ascii_strcasecmp(attr->value, "virtual")) + stype = F_VIRTUAL; + } else if (!strcmp(attr->name, "name")) + name = attr->value; + else if (!strcmp(attr->name, "path")) { +#ifdef G_OS_WIN32 + subst_char(attr->value, G_DIR_SEPARATOR, '/'); +#endif + path = attr->value; + } else if (!strcmp(attr->name, "mtime")) + mtime = strtoll(attr->value, NULL, 10); + else if (!strcmp(attr->name, "new")) + new = atoi(attr->value); + else if (!strcmp(attr->name, "unread")) + unread = atoi(attr->value); + else if (!strcmp(attr->name, "total")) + total = atoi(attr->value); + else if (!strcmp(attr->name, "no_sub")) + no_sub = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "no_select")) + no_select = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "collapsed")) + collapsed = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "threaded")) + threaded = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "sort_key")) { + if (!strcmp(attr->value, "none")) + sort_key = SORT_BY_NONE; + else if (!strcmp(attr->value, "number")) + sort_key = SORT_BY_NUMBER; + else if (!strcmp(attr->value, "size")) + sort_key = SORT_BY_SIZE; + else if (!strcmp(attr->value, "date")) + sort_key = SORT_BY_DATE; + else if (!strcmp(attr->value, "thread-date")) + sort_key = SORT_BY_TDATE; + else if (!strcmp(attr->value, "from")) + sort_key = SORT_BY_FROM; + else if (!strcmp(attr->value, "subject")) + sort_key = SORT_BY_SUBJECT; + else if (!strcmp(attr->value, "score")) + sort_key = SORT_BY_SCORE; + else if (!strcmp(attr->value, "label")) + sort_key = SORT_BY_LABEL; + else if (!strcmp(attr->value, "mark")) + sort_key = SORT_BY_MARK; + else if (!strcmp(attr->value, "unread")) + sort_key = SORT_BY_UNREAD; + else if (!strcmp(attr->value, "mime")) + sort_key = SORT_BY_MIME; + else if (!strcmp(attr->value, "to")) + sort_key = SORT_BY_TO; + } else if (!strcmp(attr->name, "sort_type")) { + if (!strcmp(attr->value, "ascending")) + sort_type = SORT_ASCENDING; + else + sort_type = SORT_DESCENDING; + } else if (!strcmp(attr->name, "qsearch_cond")) { + if (!strcmp(attr->value, "all")) + qsearch_cond_type = 0; + else if (!strcmp(attr->value, "unread")) + qsearch_cond_type = 1; + else if (!strcmp(attr->value, "mark")) + qsearch_cond_type = 2; + else if (!strcmp(attr->value, "clabel")) + qsearch_cond_type = 3; + else if (!strcmp(attr->value, "mime")) + qsearch_cond_type = 4; + else if (!strcmp(attr->value, "w1day")) + qsearch_cond_type = 5; + else if (!strcmp(attr->value, "last5")) + qsearch_cond_type = 6; + else if (!strcmp(attr->value, "last7")) + qsearch_cond_type = 7; + else if (!strcmp(attr->value, "in-addressbook")) + qsearch_cond_type = 8; + else if (!strcmp(attr->value, "last30")) + qsearch_cond_type = 9; + } else if (!strcmp(attr->name, "account_id")) { + account = account_find_from_id(atoi(attr->value)); + if (!account) g_warning("account_id: %s not found\n", + attr->value); + } else if (!strcmp(attr->name, "account_apply_sub")) + ac_apply_sub = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "to")) + auto_to = g_strdup(attr->value); + else if (!strcmp(attr->name, "use_auto_to_on_reply")) + use_auto_to_on_reply = + *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "cc")) + auto_cc = g_strdup(attr->value); + else if (!strcmp(attr->name, "bcc")) + auto_bcc = g_strdup(attr->value); + else if (!strcmp(attr->name, "replyto")) + auto_replyto = g_strdup(attr->value); + else if (!strcmp(attr->name, "trim_summary_subject")) { + trim_summary_subject = + *attr->value == '1' ? TRUE : FALSE; + } else if (!strcmp(attr->name, "trim_compose_subject")) { + trim_compose_subject = + *attr->value = '1' ? TRUE : FALSE; + } + } + + item = folder_item_new(name, path); + item->stype = stype; + item->mtime = mtime; + item->new = new; + item->unread = unread; + item->total = total; + item->no_sub = no_sub; + item->no_select = no_select; + item->collapsed = collapsed; + item->threaded = threaded; + item->sort_key = sort_key; + item->sort_type = sort_type; + item->node = node; + item->parent = FOLDER_ITEM(node->parent->data); + item->folder = folder; + switch (stype) { + case F_INBOX: folder->inbox = item; break; + case F_OUTBOX: folder->outbox = item; break; + case F_DRAFT: folder->draft = item; break; + case F_QUEUE: folder->queue = item; break; + case F_TRASH: folder->trash = item; break; + case F_JUNK: folder_set_junk(folder, item); break; + default: break; + } + item->account = account; + item->ac_apply_sub = ac_apply_sub; + item->auto_to = auto_to; + item->use_auto_to_on_reply = use_auto_to_on_reply; + item->auto_cc = auto_cc; + item->auto_bcc = auto_bcc; + item->auto_replyto = auto_replyto; + item->trim_summary_subject = trim_summary_subject; + item->trim_compose_subject = trim_compose_subject; + item->qsearch_cond_type = qsearch_cond_type; + node->data = item; + xml_free_node(xmlnode); + + return FALSE; +} + +static gboolean folder_read_folder_func(GNode *node, gpointer data) +{ + Folder *folder; + FolderItem *item; + XMLNode *xmlnode; + GList *list; + FolderType type = F_UNKNOWN; + const gchar *name = NULL; + const gchar *path = NULL; + PrefsAccount *account = NULL; + gboolean collapsed = FALSE, threaded = TRUE, ac_apply_sub = FALSE; + + if (g_node_depth(node) != 2) return FALSE; + g_return_val_if_fail(node->data != NULL, FALSE); + + xmlnode = node->data; + if (strcmp2(xmlnode->tag->tag, "folder") != 0) { + g_warning("tag name != \"folder\"\n"); + return TRUE; + } + g_node_unlink(node); + list = xmlnode->tag->attr; + for (; list != NULL; list = list->next) { + XMLAttr *attr = list->data; + + if (!attr || !attr->name || !attr->value) continue; + if (!strcmp(attr->name, "type")) { + if (!g_ascii_strcasecmp(attr->value, "mh")) + type = F_MH; + else if (!g_ascii_strcasecmp(attr->value, "mbox")) + type = F_MBOX; + else if (!g_ascii_strcasecmp(attr->value, "maildir")) + type = F_MAILDIR; + else if (!g_ascii_strcasecmp(attr->value, "imap")) + type = F_IMAP; + else if (!g_ascii_strcasecmp(attr->value, "news")) + type = F_NEWS; + } else if (!strcmp(attr->name, "name")) + name = attr->value; + else if (!strcmp(attr->name, "path")) + path = attr->value; + else if (!strcmp(attr->name, "collapsed")) + collapsed = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "threaded")) + threaded = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "account_id")) { + account = account_find_from_id(atoi(attr->value)); + if (!account) g_warning("account_id: %s not found\n", + attr->value); + } else if (!strcmp(attr->name, "account_apply_sub")) + ac_apply_sub = *attr->value == '1' ? TRUE : FALSE; + } + + folder = folder_new(type, name, path); + g_return_val_if_fail(folder != NULL, FALSE); + if (account && FOLDER_IS_REMOTE(folder)) { + folder->account = account; + account->folder = REMOTE_FOLDER(folder); + } + if (account && FOLDER_IS_LOCAL(folder)) + ac_apply_sub = TRUE; + item = FOLDER_ITEM(folder->node->data); + node->data = item; + item->node = node; + g_node_destroy(folder->node); + folder->node = node; + folder_add(folder); + item->collapsed = collapsed; + item->threaded = threaded; + item->account = account; + item->ac_apply_sub = ac_apply_sub; + + g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + folder_build_tree, folder); + + return FALSE; +} + +static gchar *folder_get_list_path(void) +{ + static gchar *filename = NULL; + + if (!filename) + filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + FOLDER_LIST, NULL); + + return filename; +} + +#define PUT_ESCAPE_STR(fp, attr, str) \ +{ \ + fputs(" " attr "=\"", fp); \ + xml_file_put_escape_str(fp, str); \ + fputs("\"", fp); \ +} + +static void folder_write_list_recursive(GNode *node, gpointer data) +{ + FILE *fp = (FILE *)data; + FolderItem *item; + gint i, depth; + static gchar *folder_type_str[] = {"mh", "mbox", "maildir", "imap", + "news", "unknown"}; + static gchar *folder_item_stype_str[] = {"normal", "inbox", "outbox", + "draft", "queue", "trash", + "junk", "virtual"}; + static gchar *sort_key_str[] = {"none", "number", "size", "date", + "thread-date", + "from", "subject", "score", "label", + "mark", "unread", "mime", "to"}; + static gchar *qsearch_cond_str[] = {"all", "unread", "mark", "clabel", + "mime", "w1day", "last5", "last7", + "in-addressbook", "last30"}; + + g_return_if_fail(node != NULL); + g_return_if_fail(fp != NULL); + + item = FOLDER_ITEM(node->data); + g_return_if_fail(item != NULL); + + depth = g_node_depth(node); + for (i = 0; i < depth; i++) + fputs(" ", fp); + if (depth == 1) { + Folder *folder = item->folder; + + fprintf(fp, "name) + PUT_ESCAPE_STR(fp, "name", folder->name); + if (FOLDER_TYPE(folder) == F_MH) + PUT_ESCAPE_STR(fp, "path", + LOCAL_FOLDER(folder)->rootpath); + if (item->collapsed && node->children) + fputs(" collapsed=\"1\"", fp); + if (folder->account) + fprintf(fp, " account_id=\"%d\"", + folder->account->account_id); + else if (item->account) + fprintf(fp, " account_id=\"%d\"", + item->account->account_id); + if (item->ac_apply_sub) + fputs(" account_apply_sub=\"1\"", fp); + } else { + fprintf(fp, "stype]); + if (item->name) + PUT_ESCAPE_STR(fp, "name", item->name); + if (item->path) + PUT_ESCAPE_STR(fp, "path", item->path); + + if (item->no_sub) + fputs(" no_sub=\"1\"", fp); + if (item->no_select) + fputs(" no_select=\"1\"", fp); + if (item->collapsed && node->children) + fputs(" collapsed=\"1\"", fp); + if (item->threaded) + fputs(" threaded=\"1\"", fp); + else + fputs(" threaded=\"0\"", fp); + + if (item->sort_key != SORT_BY_NONE) { + fprintf(fp, " sort_key=\"%s\"", + sort_key_str[item->sort_key]); + if (item->sort_type == SORT_ASCENDING) + fprintf(fp, " sort_type=\"ascending\""); + else + fprintf(fp, " sort_type=\"descending\""); + } + if (item->qsearch_cond_type > 0 && + item->qsearch_cond_type < 10) { + fprintf(fp, " qsearch_cond=\"%s\"", + qsearch_cond_str[item->qsearch_cond_type]); + } + + fprintf(fp, + " mtime=\"%lld\" new=\"%d\" unread=\"%d\" total=\"%d\"", + (gint64)item->mtime, item->new, item->unread, item->total); + + if (item->account) + fprintf(fp, " account_id=\"%d\"", + item->account->account_id); + if (item->ac_apply_sub) + fputs(" account_apply_sub=\"1\"", fp); + + if (item->auto_to) + PUT_ESCAPE_STR(fp, "to", item->auto_to); + if (item->use_auto_to_on_reply) + fputs(" use_auto_to_on_reply=\"1\"", fp); + if (item->auto_cc) + PUT_ESCAPE_STR(fp, "cc", item->auto_cc); + if (item->auto_bcc) + PUT_ESCAPE_STR(fp, "bcc", item->auto_bcc); + if (item->auto_replyto) + PUT_ESCAPE_STR(fp, "replyto", item->auto_replyto); + + if (item->trim_summary_subject) + fputs(" trim_summary_subject=\"1\"", fp); + if (item->trim_compose_subject) + fputs(" trim_compose_subject=\"1\"", fp); + } + + if (node->children) { + GNode *child; + fputs(">\n", fp); + + child = node->children; + while (child) { + GNode *cur; + + cur = child; + child = cur->next; + folder_write_list_recursive(cur, data); + } + + for (i = 0; i < depth; i++) + fputs(" ", fp); + fprintf(fp, "\n", depth == 1 ? "folder" : "folderitem"); + } else + fputs(" />\n", fp); +} + +#undef PUT_ESCAPE_STR diff --git a/libsylph/folder.h b/libsylph/folder.h new file mode 100644 index 0000000..ef9eda2 --- /dev/null +++ b/libsylph/folder.h @@ -0,0 +1,475 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2012 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __FOLDER_H__ +#define __FOLDER_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +typedef struct _Folder Folder; +typedef struct _FolderClass FolderClass; + +typedef struct _LocalFolder LocalFolder; +typedef struct _RemoteFolder RemoteFolder; +#if 0 +typedef struct _MboxFolder MboxFolder; +typedef struct _MaildirFolder MaildirFolder; +#endif + +typedef struct _FolderItem FolderItem; + +#define FOLDER(obj) ((Folder *)obj) +#define FOLDER_CLASS(obj) (FOLDER(obj)->klass) +#define FOLDER_TYPE(obj) (FOLDER(obj)->klass->type) + +#define LOCAL_FOLDER(obj) ((LocalFolder *)obj) +#define REMOTE_FOLDER(obj) ((RemoteFolder *)obj) + +#define FOLDER_IS_LOCAL(obj) (FOLDER_TYPE(obj) == F_MH || \ + FOLDER_TYPE(obj) == F_MBOX || \ + FOLDER_TYPE(obj) == F_MAILDIR) +#define FOLDER_IS_REMOTE(obj) (FOLDER_TYPE(obj) == F_IMAP || \ + FOLDER_TYPE(obj) == F_NEWS) + +#if 0 +#define MBOX_FOLDER(obj) ((MboxFolder *)obj) +#define MAILDIR_FOLDER(obj) ((MaildirFolder *)obj) +#endif + +#define FOLDER_ITEM(obj) ((FolderItem *)obj) + +#define FOLDER_ITEM_CAN_ADD(obj) \ + ((obj) && FOLDER_ITEM(obj)->folder && \ + FOLDER_ITEM(obj)->path && \ + (FOLDER_IS_LOCAL(FOLDER_ITEM(obj)->folder) || \ + FOLDER_TYPE(FOLDER_ITEM(obj)->folder) == F_IMAP) && \ + !FOLDER_ITEM(obj)->no_select) + +#define FOLDER_ITEM_IS_SENT_FOLDER(obj) ((obj) && \ + ((obj)->stype == F_OUTBOX || \ + (obj)->stype == F_DRAFT || \ + (obj)->stype == F_QUEUE)) + +typedef enum +{ + F_MH, + F_MBOX, + F_MAILDIR, + F_IMAP, + F_NEWS, + F_UNKNOWN +} FolderType; + +typedef enum +{ + F_NORMAL, + F_INBOX, + F_OUTBOX, + F_DRAFT, + F_QUEUE, + F_TRASH, + F_JUNK, + F_VIRTUAL +} SpecialFolderItemType; + +typedef enum +{ + SORT_BY_NONE, + SORT_BY_NUMBER, + SORT_BY_SIZE, + SORT_BY_DATE, + SORT_BY_TDATE, + SORT_BY_FROM, + SORT_BY_SUBJECT, + SORT_BY_SCORE, + SORT_BY_LABEL, + SORT_BY_MARK, + SORT_BY_UNREAD, + SORT_BY_MIME, + SORT_BY_TO +} FolderSortKey; + +typedef enum +{ + SORT_ASCENDING, + SORT_DESCENDING +} FolderSortType; + +typedef void (*FolderUIFunc) (Folder *folder, + FolderItem *item, + gpointer data); +typedef gboolean (*FolderUIFunc2) (Folder *folder, + FolderItem *item, + guint count, + guint total, + gpointer data); +typedef void (*FolderDestroyNotify) (Folder *folder, + FolderItem *item, + gpointer data); + +#include "prefs_account.h" +#include "session.h" +#include "procmsg.h" +#include "utils.h" + +struct _Folder +{ + FolderClass *klass; + + gchar *name; + PrefsAccount *account; + + FolderItem *inbox; + FolderItem *outbox; + FolderItem *draft; + FolderItem *queue; + FolderItem *trash; + + FolderUIFunc ui_func; + gpointer ui_func_data; + + GNode *node; + + gpointer data; +}; + +struct _FolderClass +{ + FolderType type; + + /* virtual functions */ + Folder * (*folder_new) (const gchar *name, + const gchar *path); + void (*destroy) (Folder *folder); + + gint (*scan_tree) (Folder *folder); + gint (*create_tree) (Folder *folder); + + GSList * (*get_msg_list) (Folder *folder, + FolderItem *item, + gboolean use_cache); + GSList * (*get_uncached_msg_list) + (Folder *folder, + FolderItem *item); + /* return value is filename encoding */ + gchar * (*fetch_msg) (Folder *folder, + FolderItem *item, + gint num); + MsgInfo * (*get_msginfo) (Folder *folder, + FolderItem *item, + gint num); + gint (*add_msg) (Folder *folder, + FolderItem *dest, + const gchar *file, + MsgFlags *flags, + gboolean remove_source); + gint (*add_msgs) (Folder *folder, + FolderItem *dest, + GSList *file_list, + gboolean remove_source, + gint *first); + gint (*add_msg_msginfo) (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo, + gboolean remove_source); + gint (*add_msgs_msginfo) (Folder *folder, + FolderItem *dest, + GSList *msginfo_list, + gboolean remove_source, + gint *first); + gint (*move_msg) (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo); + gint (*move_msgs) (Folder *folder, + FolderItem *dest, + GSList *msglist); + gint (*copy_msg) (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo); + gint (*copy_msgs) (Folder *folder, + FolderItem *dest, + GSList *msglist); + gint (*remove_msg) (Folder *folder, + FolderItem *item, + MsgInfo *msginfo); + gint (*remove_msgs) (Folder *folder, + FolderItem *item, + GSList *msglist); + gint (*remove_all_msg) (Folder *folder, + FolderItem *item); + gboolean (*is_msg_changed) (Folder *folder, + FolderItem *item, + MsgInfo *msginfo); + gint (*close) (Folder *folder, + FolderItem *item); + gint (*scan) (Folder *folder, + FolderItem *item); + + FolderItem * (*create_folder) (Folder *folder, + FolderItem *parent, + const gchar *name); + gint (*rename_folder) (Folder *folder, + FolderItem *item, + const gchar *name); + gint (*move_folder) (Folder *folder, + FolderItem *item, + FolderItem *new_parent); + gint (*remove_folder) (Folder *folder, + FolderItem *item); +}; + +struct _LocalFolder +{ + Folder folder; + + gchar *rootpath; +}; + +struct _RemoteFolder +{ + Folder folder; + + Session *session; + + guint remove_cache_on_destroy : 1; + guint dummy1 : 1; + guint dummy2 : 1; + guint dummy3 : 1; +}; + +#if 0 +struct _MboxFolder +{ + LocalFolder lfolder; +}; + +struct _MaildirFolder +{ + LocalFolder lfolder; +}; +#endif + +struct _FolderItem +{ + SpecialFolderItemType stype; + + gchar *name; /* UTF-8 */ + gchar *path; /* UTF-8 */ + + stime_t mtime; + + gint new; + gint unread; + gint total; + gint unmarked_num; + + gint last_num; + + /* special flags */ + guint no_sub : 1; /* no child allowed? */ + guint no_select : 1; /* not selectable? */ + guint collapsed : 1; /* collapsed item */ + guint threaded : 1; /* threaded folder view */ + + guint opened : 1; /* opened by summary view */ + guint updated : 1; /* folderview should be updated */ + + guint cache_dirty : 1; /* cache file needs to be updated */ + guint mark_dirty : 1; /* mark file needs to be updated */ + + FolderSortKey sort_key; + FolderSortType sort_type; + + GNode *node; + + FolderItem *parent; + + Folder *folder; + + PrefsAccount *account; + + gboolean ac_apply_sub; + + gchar *auto_to; + gboolean use_auto_to_on_reply; + gchar *auto_cc; + gchar *auto_bcc; + gchar *auto_replyto; + + gboolean trim_summary_subject; + gboolean trim_compose_subject; + + GSList *cache_queue; + GSList *mark_queue; + + guint last_selected; + gint qsearch_cond_type; + + gpointer data; +}; + +Folder *folder_new (FolderType type, + const gchar *name, + const gchar *path); +void folder_local_folder_init (Folder *folder, + const gchar *name, + const gchar *path); +void folder_remote_folder_init (Folder *folder, + const gchar *name, + const gchar *path); + +void folder_destroy (Folder *folder); +void folder_local_folder_destroy (LocalFolder *lfolder); +void folder_remote_folder_destroy(RemoteFolder *rfolder); + +gint folder_remote_folder_destroy_all_sessions (void); + +gboolean folder_remote_folder_is_session_active + (RemoteFolder *rfolder); +gboolean folder_remote_folder_active_session_exist (void); + +gint folder_scan_tree (Folder *folder); +gint folder_create_tree (Folder *folder); + +FolderItem *folder_item_new (const gchar *name, + const gchar *path); +void folder_item_append (FolderItem *parent, + FolderItem *item); +FolderItem *folder_item_copy (FolderItem *item); +void folder_item_remove (FolderItem *item); +void folder_item_remove_children (FolderItem *item); +void folder_item_destroy (FolderItem *item); + +gint folder_item_compare (FolderItem *item_a, + FolderItem *item_b); + +void folder_set_ui_func (Folder *folder, + FolderUIFunc func, + gpointer data); +void folder_set_ui_func2 (Folder *folder, + FolderUIFunc2 func, + gpointer data); +FolderUIFunc2 folder_get_ui_func2 (Folder *folder); +gboolean folder_call_ui_func2 (Folder *folder, + FolderItem *item, + guint count, + guint total); + +void folder_set_name (Folder *folder, + const gchar *name); +void folder_tree_destroy (Folder *folder); + +void folder_add (Folder *folder); + +GList *folder_get_list (void); +gint folder_read_list (void); +void folder_write_list (void); + +gchar *folder_get_status (GPtrArray *folders, + gboolean full); + +Folder *folder_find_from_path (const gchar *path); +Folder *folder_find_from_name (const gchar *name, + FolderType type); +FolderItem *folder_find_item_from_path (const gchar *path); +FolderItem *folder_find_child_item_by_name (FolderItem *item, + const gchar *name); +gchar *folder_get_identifier (Folder *folder); +gchar *folder_item_get_identifier (FolderItem *item); +FolderItem *folder_find_item_from_identifier (const gchar *identifier); +FolderItem *folder_find_item_and_num_from_id (const gchar *identifier, + gint *num); + +Folder *folder_get_default_folder (void); +FolderItem *folder_get_default_inbox (void); +FolderItem *folder_get_default_outbox (void); +FolderItem *folder_get_default_draft (void); +FolderItem *folder_get_default_queue (void); +FolderItem *folder_get_default_trash (void); +FolderItem *folder_get_default_junk (void); + +gboolean folder_item_is_trash (FolderItem *item); + +FolderItem *folder_get_junk (Folder *folder); +void folder_set_junk (Folder *folder, + FolderItem *item); + +void folder_set_missing_folders (void); +void folder_unref_account_all (PrefsAccount *account); + +/* return value is filename encoding */ +gchar *folder_get_path (Folder *folder); +gchar *folder_item_get_path (FolderItem *item); + +gint folder_item_scan (FolderItem *item); +void folder_item_scan_foreach (GHashTable *table); +GSList *folder_item_get_msg_list (FolderItem *item, + gboolean use_cache); +GSList *folder_item_get_uncached_msg_list + (FolderItem *item); +/* return value is filename encoding */ +gchar *folder_item_fetch_msg (FolderItem *item, + gint num); +gint folder_item_fetch_all_msg (FolderItem *item); +MsgInfo *folder_item_get_msginfo (FolderItem *item, + gint num); +gint folder_item_add_msg (FolderItem *dest, + const gchar *file, + MsgFlags *flags, + gboolean remove_source); +gint folder_item_add_msgs (FolderItem *dest, + GSList *file_list, + gboolean remove_source, + gint *first); +gint folder_item_add_msg_msginfo (FolderItem *dest, + MsgInfo *msginfo, + gboolean remove_source); +gint folder_item_add_msgs_msginfo (FolderItem *dest, + GSList *msginfo_list, + gboolean remove_source, + gint *first); +gint folder_item_move_msg (FolderItem *dest, + MsgInfo *msginfo); +gint folder_item_move_msgs (FolderItem *dest, + GSList *msglist); +gint folder_item_copy_msg (FolderItem *dest, + MsgInfo *msginfo); +gint folder_item_copy_msgs (FolderItem *dest, + GSList *msglist); +gint folder_item_remove_msg (FolderItem *item, + MsgInfo *msginfo); +gint folder_item_remove_msgs (FolderItem *item, + GSList *msglist); +gint folder_item_remove_all_msg (FolderItem *item); + +gboolean folder_item_is_msg_changed (FolderItem *item, + MsgInfo *msginfo); + +/* return value is filename encoding */ +gchar *folder_item_get_cache_file (FolderItem *item); +gchar *folder_item_get_mark_file (FolderItem *item); + +gint folder_item_close (FolderItem *item); + +#endif /* __FOLDER_H__ */ diff --git a/libsylph/html.c b/libsylph/html.c new file mode 100644 index 0000000..a86c07b --- /dev/null +++ b/libsylph/html.c @@ -0,0 +1,791 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2011 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include + +#include "html.h" +#include "codeconv.h" +#include "utils.h" + +#define HTMLBUFSIZE 8192 +#define HR_STR "------------------------------------------------" + +typedef struct _HTMLSymbol HTMLSymbol; + +struct _HTMLSymbol +{ + gchar *const key; + gchar *const val; +}; + +static HTMLSymbol symbol_list[] = { + {"<" , "<"}, + {">" , ">"}, + {"&" , "&"}, + {""" , "\""} +}; + +/*   - ÿ */ +static HTMLSymbol latin_symbol_list[] = { + {" " , " "}, + /* {" " , "\302\240"}, */ + {"¡" , "\302\241"}, + {"¢" , "\302\242"}, + {"£" , "\302\243"}, + {"¤", "\302\244"}, + {"¥" , "\302\245"}, + {"¦", "\302\246"}, + {"§" , "\302\247"}, + {"¨" , "\302\250"}, + {"©" , "\302\251"}, + {"ª" , "\302\252"}, + {"«" , "\302\253"}, + {"¬" , "\302\254"}, + {"­" , "\302\255"}, + {"®" , "\302\256"}, + {"¯" , "\302\257"}, + {"°" , "\302\260"}, + {"&plusm;" , "\302\261"}, + {"²" , "\302\262"}, + {"³" , "\302\263"}, + {"´" , "\302\264"}, + {"µ" , "\302\265"}, + {"¶" , "\302\266"}, + {"·", "\302\267"}, + {"¸" , "\302\270"}, + {"¹" , "\302\271"}, + {"º" , "\302\272"}, + {"»" , "\302\273"}, + {"¼", "\302\274"}, + {"½", "\302\275"}, + {"¾", "\302\276"}, + {"¿", "\302\277"}, + + {"À", "\303\200"}, + {"Á", "\303\201"}, + {"Â" , "\303\202"}, + {"Ã", "\303\203"}, + {"Ä" , "\303\204"}, + {"Å" , "\303\205"}, + {"Æ" , "\303\206"}, + {"Ç", "\303\207"}, + {"È", "\303\210"}, + {"É", "\303\211"}, + {"Ê" , "\303\212"}, + {"Ë" , "\303\213"}, + {"Ì", "\303\214"}, + {"Í", "\303\215"}, + {"Î" , "\303\216"}, + {"Ï" , "\303\217"}, + {"Ð" , "\303\220"}, + {"Ñ", "\303\221"}, + {"Ò", "\303\222"}, + {"Ó", "\303\223"}, + {"Ô" , "\303\224"}, + {"Õ", "\303\225"}, + {"Ö" , "\303\226"}, + {"×" , "\303\227"}, + {"Ø", "\303\230"}, + {"Ù", "\303\231"}, + {"Ú", "\303\232"}, + {"Û" , "\303\233"}, + {"Ü" , "\303\234"}, + {"Ý", "\303\235"}, + {"Þ" , "\303\236"}, + {"ß" , "\303\237"}, + {"à", "\303\240"}, + {"á", "\303\241"}, + {"â" , "\303\242"}, + {"ã", "\303\243"}, + {"ä" , "\303\244"}, + {"å" , "\303\245"}, + {"æ" , "\303\246"}, + {"ç", "\303\247"}, + {"è", "\303\250"}, + {"é", "\303\251"}, + {"ê" , "\303\252"}, + {"ë" , "\303\253"}, + {"ì", "\303\254"}, + {"í", "\303\255"}, + {"î" , "\303\256"}, + {"ï" , "\303\257"}, + {"ð" , "\303\260"}, + {"ñ", "\303\261"}, + {"ò", "\303\262"}, + {"ó", "\303\263"}, + {"ô" , "\303\264"}, + {"õ", "\303\265"}, + {"ö" , "\303\266"}, + {"÷", "\303\267"}, + {"ø", "\303\270"}, + {"ù", "\303\271"}, + {"ú", "\303\272"}, + {"û" , "\303\273"}, + {"ü" , "\303\274"}, + {"ý", "\303\275"}, + {"þ" , "\303\276"}, + {"ÿ" , "\303\277"} +}; + +static HTMLSymbol other_symbol_list[] = { + /* Non-standard? */ + {"…" , "..."}, + {"’" , "'"}, + {"–" , "-"}, + {"™" , "\xe2\x84\xa2"}, + {"œ" , "\xc5\x93"}, + + /* Symbolic characters */ + {"™" , "\xe2\x84\xa2"}, + + /* Latin extended */ + {"Œ" , "\xc5\x92"}, + {"œ" , "\xc5\x93"}, + {"Š", "\xc5\xa0"}, + {"š", "\xc5\xa1"}, + {"Ÿ" , "\xc5\xb8"}, + {"ˆ" , "\xcb\x86"}, + {"˜" , "\xcb\x9c"}, + {"ƒ" , "\xc6\x92"}, +}; + +static GHashTable *default_symbol_table; + +static HTMLState html_read_line (HTMLParser *parser); + +static void html_append_char (HTMLParser *parser, + gchar ch); +static void html_append_str (HTMLParser *parser, + const gchar *str, + gint len); + +static gchar *html_find_char (HTMLParser *parser, + gchar ch); +static gchar *html_find_str (HTMLParser *parser, + const gchar *str); +static gchar *html_find_str_case (HTMLParser *parser, + const gchar *str); + +static HTMLState html_parse_tag (HTMLParser *parser); +static void html_parse_special (HTMLParser *parser); +static void html_get_parenthesis (HTMLParser *parser, + gchar *buf, + gint len); + +static gchar *html_unescape_str (HTMLParser *parser, + const gchar *str); + + +HTMLParser *html_parser_new(FILE *fp, CodeConverter *conv) +{ + HTMLParser *parser; + + g_return_val_if_fail(fp != NULL, NULL); + g_return_val_if_fail(conv != NULL, NULL); + + parser = g_new0(HTMLParser, 1); + parser->fp = fp; + parser->conv = conv; + parser->str = g_string_new(NULL); + parser->buf = g_string_new(NULL); + parser->bufp = parser->buf->str; + parser->state = HTML_NORMAL; + parser->href = NULL; + parser->newline = TRUE; + parser->empty_line = TRUE; + parser->space = FALSE; + parser->pre = FALSE; + parser->blockquote = 0; + +#define SYMBOL_TABLE_ADD(table, list) \ +{ \ + gint i; \ + \ + for (i = 0; i < sizeof(list) / sizeof(list[0]); i++) \ + g_hash_table_insert(table, list[i].key, list[i].val); \ +} + + if (!default_symbol_table) { + default_symbol_table = + g_hash_table_new(g_str_hash, g_str_equal); + SYMBOL_TABLE_ADD(default_symbol_table, symbol_list); + SYMBOL_TABLE_ADD(default_symbol_table, latin_symbol_list); + SYMBOL_TABLE_ADD(default_symbol_table, other_symbol_list); + } + +#undef SYMBOL_TABLE_ADD + + parser->symbol_table = default_symbol_table; + + return parser; +} + +void html_parser_destroy(HTMLParser *parser) +{ + g_string_free(parser->str, TRUE); + g_string_free(parser->buf, TRUE); + g_free(parser->href); + g_free(parser); +} + +const gchar *html_parse(HTMLParser *parser) +{ + parser->state = HTML_NORMAL; + g_string_truncate(parser->str, 0); + + if (*parser->bufp == '\0') { + g_string_truncate(parser->buf, 0); + parser->bufp = parser->buf->str; + if (html_read_line(parser) == HTML_EOF) + return NULL; + } + + while (*parser->bufp != '\0') { + switch (*parser->bufp) { + case '<': + if (parser->str->len == 0) + html_parse_tag(parser); + else + return parser->str->str; + break; + case '&': + html_parse_special(parser); + break; + case ' ': + case '\t': + case '\r': + case '\n': + if (parser->bufp[0] == '\r' && parser->bufp[1] == '\n') + parser->bufp++; + + if (!parser->pre) { + if (!parser->newline) + parser->space = TRUE; + + parser->bufp++; + break; + } + /* fallthrough */ + default: + html_append_char(parser, *parser->bufp++); + } + } + + return parser->str->str; +} + +static HTMLState html_read_line(HTMLParser *parser) +{ + gchar buf[HTMLBUFSIZE]; + gchar *conv_str; + gint index; + + if (fgets(buf, sizeof(buf), parser->fp) == NULL) { + parser->state = HTML_EOF; + return HTML_EOF; + } + + conv_str = conv_convert(parser->conv, buf); + if (!conv_str) { + index = parser->bufp - parser->buf->str; + + conv_str = conv_utf8todisp(buf, NULL); + g_string_append(parser->buf, conv_str); + g_free(conv_str); + + parser->bufp = parser->buf->str + index; + + return HTML_CONV_FAILED; + } + + index = parser->bufp - parser->buf->str; + + g_string_append(parser->buf, conv_str); + g_free(conv_str); + + parser->bufp = parser->buf->str + index; + + return HTML_NORMAL; +} + +static void html_append_char(HTMLParser *parser, gchar ch) +{ + GString *str = parser->str; + const gchar *bq_prefix = NULL; + + if (!parser->pre && parser->space) { + g_string_append_c(str, ' '); + parser->space = FALSE; + } + + if (parser->newline && parser->blockquote > 0) + bq_prefix = " "; + + parser->empty_line = FALSE; + if (ch == '\n') { + parser->newline = TRUE; + if (str->len > 0 && str->str[str->len - 1] == '\n') + parser->empty_line = TRUE; + } else + parser->newline = FALSE; + + if (bq_prefix) { + gint i; + for (i = 0; i < parser->blockquote; i++) + g_string_append(str, bq_prefix); + } + g_string_append_c(str, ch); +} + +static void html_append_str(HTMLParser *parser, const gchar *str, gint len) +{ + GString *string = parser->str; + const gchar *bq_prefix = NULL; + + if (!parser->pre && parser->space) { + g_string_append_c(string, ' '); + parser->space = FALSE; + } + + if (len == 0) return; + + if (parser->newline && parser->blockquote > 0) + bq_prefix = " "; + + if (bq_prefix) { + gint i; + for (i = 0; i < parser->blockquote; i++) + g_string_append(string, bq_prefix); + } + + if (len < 0) + g_string_append(string, str); + else + g_string_append_len(string, str, len); + + parser->empty_line = FALSE; + if (string->len > 0 && string->str[string->len - 1] == '\n') { + parser->newline = TRUE; + if (string->len > 1 && string->str[string->len - 2] == '\n') + parser->empty_line = TRUE; + } else + parser->newline = FALSE; +} + +static gchar *html_find_char(HTMLParser *parser, gchar ch) +{ + gchar *p; + + while ((p = strchr(parser->bufp, ch)) == NULL) { + if (html_read_line(parser) == HTML_EOF) + return NULL; + } + + return p; +} + +static gchar *html_find_str(HTMLParser *parser, const gchar *str) +{ + gchar *p; + + while ((p = strstr(parser->bufp, str)) == NULL) { + if (html_read_line(parser) == HTML_EOF) + return NULL; + } + + return p; +} + +static gchar *html_find_str_case(HTMLParser *parser, const gchar *str) +{ + gchar *p; + + while ((p = strcasestr(parser->bufp, str)) == NULL) { + if (html_read_line(parser) == HTML_EOF) + return NULL; + } + + return p; +} + +static HTMLTag *html_get_tag(const gchar *str) +{ + HTMLTag *tag; + gchar *tmp; + gchar *tmpp; + + g_return_val_if_fail(str != NULL, NULL); + + if (*str == '\0' || *str == '!') return NULL; + + tmp = g_strdup(str); + + tag = g_new0(HTMLTag, 1); + + for (tmpp = tmp; *tmpp != '\0' && !g_ascii_isspace(*tmpp); tmpp++) { + if (tmpp > tmp && *tmpp == '/') { + *tmpp = '\0'; + break; + } + } + + if (*tmpp == '\0') { + g_strdown(tmp); + tag->name = tmp; + return tag; + } else { + *tmpp++ = '\0'; + g_strdown(tmp); + tag->name = g_strdup(tmp); + } + + while (*tmpp != '\0') { + HTMLAttr *attr; + gchar *attr_name; + gchar *attr_value; + gchar *p; + gchar quote; + + while (g_ascii_isspace(*tmpp)) tmpp++; + if (tmpp > tmp && *tmpp == '/') + break; + attr_name = tmpp; + + while (*tmpp != '\0' && !g_ascii_isspace(*tmpp) && + *tmpp != '=') + tmpp++; + if (*tmpp != '\0' && *tmpp != '=') { + *tmpp++ = '\0'; + while (g_ascii_isspace(*tmpp)) tmpp++; + } + + if (*tmpp == '=') { + *tmpp++ = '\0'; + while (g_ascii_isspace(*tmpp)) tmpp++; + + if (*tmpp == '"' || *tmpp == '\'') { + /* name="value" */ + quote = *tmpp; + tmpp++; + attr_value = tmpp; + if ((p = strchr(attr_value, quote)) == NULL) { + g_warning("html_get_tag(): syntax error in tag: '%s'\n", str); + break; + } + tmpp = p; + *tmpp++ = '\0'; + while (g_ascii_isspace(*tmpp)) tmpp++; + } else { + /* name=value */ + attr_value = tmpp; + while (*tmpp != '\0' && !g_ascii_isspace(*tmpp)) tmpp++; + if (*tmpp != '\0') + *tmpp++ = '\0'; + } + } else + attr_value = ""; + + g_strchomp(attr_name); + g_strdown(attr_name); + attr = g_new(HTMLAttr, 1); + attr->name = g_strdup(attr_name); + attr->value = g_strdup(attr_value); + tag->attr = g_list_append(tag->attr, attr); + } + + g_free(tmp); + + return tag; +} + +static void html_free_tag(HTMLTag *tag) +{ + if (!tag) return; + + g_free(tag->name); + while (tag->attr != NULL) { + HTMLAttr *attr = (HTMLAttr *)tag->attr->data; + g_free(attr->name); + g_free(attr->value); + g_free(attr); + tag->attr = g_list_remove(tag->attr, tag->attr->data); + } + g_free(tag); +} + +static HTMLState html_parse_tag(HTMLParser *parser) +{ + gchar buf[HTMLBUFSIZE]; + HTMLTag *tag; + + html_get_parenthesis(parser, buf, sizeof(buf)); + + tag = html_get_tag(buf); + + parser->state = HTML_UNKNOWN; + if (!tag) return HTML_UNKNOWN; + + if (!strcmp(tag->name, "br")) { + parser->space = FALSE; + html_append_char(parser, '\n'); + parser->state = HTML_BR; + } else if (!strcmp(tag->name, "a")) { + GList *cur; + + for (cur = tag->attr; cur != NULL; cur = cur->next) { + HTMLAttr *attr = (HTMLAttr *)cur->data; + + if (attr && !strcmp(attr->name, "href")) { + g_free(parser->href); + parser->href = html_unescape_str(parser, attr->value); + parser->state = HTML_HREF; + break; + } + } + } else if (!strcmp(tag->name, "/a")) { + g_free(parser->href); + parser->href = NULL; + parser->state = HTML_NORMAL; + } else if (!strcmp(tag->name, "p")) { + parser->space = FALSE; + if (!parser->empty_line) { + parser->space = FALSE; + if (!parser->newline) html_append_char(parser, '\n'); + html_append_char(parser, '\n'); + } + parser->state = HTML_PAR; + } else if (!strcmp(tag->name, "pre")) { + parser->pre = TRUE; + parser->state = HTML_PRE; + } else if (!strcmp(tag->name, "/pre")) { + parser->pre = FALSE; + parser->state = HTML_NORMAL; + } else if (!strcmp(tag->name, "blockquote")) { + parser->blockquote++; + parser->state = HTML_BLOCKQUOTE; + } else if (!strcmp(tag->name, "/blockquote")) { + parser->blockquote--; + if (parser->blockquote < 0) + parser->blockquote = 0; + parser->state = HTML_NORMAL; + } else if (!strcmp(tag->name, "hr")) { + if (!parser->newline) { + parser->space = FALSE; + html_append_char(parser, '\n'); + } + html_append_str(parser, HR_STR "\n", -1); + parser->state = HTML_HR; + } else if (!strcmp(tag->name, "div") || + !strcmp(tag->name, "ul") || + !strcmp(tag->name, "li") || + !strcmp(tag->name, "table") || + !strcmp(tag->name, "tr") || + (tag->name[0] == 'h' && g_ascii_isdigit(tag->name[1]))) { + if (!parser->newline) { + parser->space = FALSE; + html_append_char(parser, '\n'); + } + parser->state = HTML_NORMAL; + } else if (!strcmp(tag->name, "/table") || + (tag->name[0] == '/' && + tag->name[1] == 'h' && + g_ascii_isdigit(tag->name[1]))) { + if (!parser->empty_line) { + parser->space = FALSE; + if (!parser->newline) html_append_char(parser, '\n'); + html_append_char(parser, '\n'); + } + parser->state = HTML_NORMAL; + } else if (!strcmp(tag->name, "/div") || + !strcmp(tag->name, "/ul") || + !strcmp(tag->name, "/li")) { + if (!parser->newline) { + parser->space = FALSE; + html_append_char(parser, '\n'); + } + parser->state = HTML_NORMAL; + } + + html_free_tag(tag); + + return parser->state; +} + +static void html_parse_special(HTMLParser *parser) +{ + gchar symbol_name[9]; + gint n; + const gchar *val; + + parser->state = HTML_UNKNOWN; + g_return_if_fail(*parser->bufp == '&'); + + /* &foo; */ + for (n = 0; parser->bufp[n] != '\0' && parser->bufp[n] != ';'; n++) + ; + if (n > 7 || parser->bufp[n] != ';') { + /* output literal `&' */ + html_append_char(parser, *parser->bufp++); + parser->state = HTML_NORMAL; + return; + } + strncpy2(symbol_name, parser->bufp, n + 2); + parser->bufp += n + 1; + + if ((val = g_hash_table_lookup(parser->symbol_table, symbol_name)) + != NULL) { + html_append_str(parser, val, -1); + parser->state = HTML_NORMAL; + return; + } else if (symbol_name[1] == '#' && g_ascii_isdigit(symbol_name[2])) { + gint ch; + + ch = atoi(symbol_name + 2); + if (ch < 128 && g_ascii_isprint(ch)) { + html_append_char(parser, ch); + parser->state = HTML_NORMAL; + return; + } else { + /* ISO 10646 to UTF-8 */ + gchar buf[6]; + gint len; + + len = g_unichar_to_utf8((gunichar)ch, buf); + if (len > 0) { + html_append_str(parser, buf, len); + parser->state = HTML_NORMAL; + return; + } + } + } + + html_append_str(parser, symbol_name, -1); +} + +static void html_get_parenthesis(HTMLParser *parser, gchar *buf, gint len) +{ + gchar *p; + + buf[0] = '\0'; + g_return_if_fail(*parser->bufp == '<'); + + /* ignore comment / CSS / script stuff */ + if (!strncmp(parser->bufp, "")) != NULL) + parser->bufp = p + 3; + return; + } + if (!g_ascii_strncasecmp(parser->bufp, "bufp += 6; + if ((p = html_find_str_case(parser, "bufp = p + 7; + if ((p = html_find_char(parser, '>')) != NULL) + parser->bufp = p + 1; + } + return; + } + if (!g_ascii_strncasecmp(parser->bufp, "bufp += 7; + if ((p = html_find_str_case(parser, "bufp = p + 8; + if ((p = html_find_char(parser, '>')) != NULL) + parser->bufp = p + 1; + } + return; + } + + parser->bufp++; + if ((p = html_find_char(parser, '>')) == NULL) + return; + + strncpy2(buf, parser->bufp, MIN(p - parser->bufp + 1, len)); + g_strstrip(buf); + parser->bufp = p + 1; +} + +static gchar *html_unescape_str(HTMLParser *parser, const gchar *str) +{ + const gchar *p = str; + gchar symbol_name[9]; + gint n; + const gchar *val; + gchar *unescape_str; + gchar *up; + + if (!str) + return NULL; + + up = unescape_str = g_malloc(strlen(str) + 1); + + while (*p != '\0') { + switch (*p) { + case '&': + for (n = 0; p[n] != '\0' && p[n] != ';'; n++) + ; + if (n > 7 || p[n] != ';') { + *up++ = *p++; + break; + } + strncpy2(symbol_name, p, n + 2); + p += n + 1; + + if ((val = g_hash_table_lookup(parser->symbol_table, symbol_name)) != NULL) { + gint len = strlen(val); + if (len <= n + 1) { + strcpy(up, val); + up += len; + } else { + strcpy(up, symbol_name); + up += n + 1; + } + } else if (symbol_name[1] == '#' && g_ascii_isdigit(symbol_name[2])) { + gint ch; + + ch = atoi(symbol_name + 2); + if (ch < 128 && g_ascii_isprint(ch)) { + *up++ = ch; + } else { + /* ISO 10646 to UTF-8 */ + gchar buf[6]; + gint len; + + len = g_unichar_to_utf8((gunichar)ch, buf); + if (len > 0 && len <= 6 && len <= n + 1) { + memcpy(up, buf, len); + up += len; + } else { + strcpy(up, symbol_name); + up += n + 1; + } + } + } + + break; + default: + *up++ = *p++; + } + } + + *up = '\0'; + return unescape_str; +} diff --git a/libsylph/html.h b/libsylph/html.h new file mode 100644 index 0000000..bdf9257 --- /dev/null +++ b/libsylph/html.h @@ -0,0 +1,89 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __HTML_H__ +#define __HTML_H__ + +#include +#include + +#include "codeconv.h" + +typedef enum +{ + HTML_NORMAL, + HTML_PAR, + HTML_BR, + HTML_HR, + HTML_HREF, + HTML_IMG, + HTML_FONT, + HTML_PRE, + HTML_UNKNOWN, + HTML_CONV_FAILED, + HTML_ERR, + HTML_EOF, + HTML_BLOCKQUOTE +} HTMLState; + +typedef struct _HTMLParser HTMLParser; +typedef struct _HTMLAttr HTMLAttr; +typedef struct _HTMLTag HTMLTag; + +struct _HTMLParser +{ + FILE *fp; + CodeConverter *conv; + + GHashTable *symbol_table; + + GString *str; + GString *buf; + + gchar *bufp; + + HTMLState state; + + gchar *href; + + gboolean newline; + gboolean empty_line; + gboolean space; + gboolean pre; + gint blockquote; +}; + +struct _HTMLAttr +{ + gchar *name; + gchar *value; +}; + +struct _HTMLTag +{ + gchar *name; + GList *attr; +}; + +HTMLParser *html_parser_new (FILE *fp, + CodeConverter *conv); +void html_parser_destroy (HTMLParser *parser); +const gchar *html_parse (HTMLParser *parser); + +#endif /* __HTML_H__ */ diff --git a/libsylph/imap.c b/libsylph/imap.c new file mode 100644 index 0000000..aa0d737 --- /dev/null +++ b/libsylph/imap.c @@ -0,0 +1,5215 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2013 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_ICONV +# include +#endif + +#include "sylmain.h" +#include "imap.h" +#include "socket.h" +#include "socks.h" +#include "ssl.h" +#include "recv.h" +#include "procmsg.h" +#include "procheader.h" +#include "folder.h" +#include "prefs_account.h" +#include "codeconv.h" +#include "md5_hmac.h" +#include "base64.h" +#include "utils.h" +#include "prefs_common.h" +#include "virtual.h" + +#define IMAP4_PORT 143 +#if USE_SSL +#define IMAPS_PORT 993 +#endif + +#define IMAP_COPY_LIMIT 200 +#define IMAP_CMD_LIMIT 1000 + +#define QUOTE_IF_REQUIRED(out, str) \ +{ \ + if (!str || *str == '\0') { \ + Xstrdup_a(out, "\"\"", return IMAP_ERROR); \ + } else if (strpbrk(str, " \t(){}[]%&*\"\\") != NULL) { \ + gchar *__tmp; \ + gint len; \ + const gchar *p; \ + gchar *tp; \ + \ + len = strlen(str) * 2 + 3; \ + Xalloca(__tmp, len, return IMAP_ERROR); \ + tp = __tmp; \ + *tp++ = '\"'; \ + for (p = str; *p != '\0'; p++) { \ + if (*p == '\"' || *p == '\\') \ + *tp++ = '\\'; \ + *tp++ = *p; \ + } \ + *tp++ = '\"'; \ + *tp = '\0'; \ + out = __tmp; \ + } else { \ + Xstrdup_a(out, str, return IMAP_ERROR); \ + } \ +} + +typedef gint (*IMAPThreadFunc) (IMAPSession *session, + gpointer data); +typedef gint (*IMAPProgressFunc) (IMAPSession *session, + gint count, + gint total, + gpointer data); + +typedef struct _IMAPRealSession +{ + IMAPSession imap_session; +#if USE_THREADS + GThreadPool *pool; + IMAPThreadFunc thread_func; + gpointer thread_data; + gboolean is_running; + gint prog_count; + gint prog_total; + gint flag; + gint retval; +#endif +} IMAPRealSession; + +static GList *session_list = NULL; + +static void imap_folder_init (Folder *folder, + const gchar *name, + const gchar *path); + +static Folder *imap_folder_new (const gchar *name, + const gchar *path); +static void imap_folder_destroy (Folder *folder); + +static Session *imap_session_new (PrefsAccount *account); +static gint imap_session_connect (IMAPSession *session); +static gint imap_session_reconnect (IMAPSession *session); +static void imap_session_destroy (Session *session); +/* static void imap_session_destroy_all (void); */ + +static gint imap_search_flags (IMAPSession *session, + GArray **uids, + GHashTable **flags_table); +static gint imap_fetch_flags (IMAPSession *session, + GArray **uids, + GHashTable **flags_table); + +static GSList *imap_get_msg_list (Folder *folder, + FolderItem *item, + gboolean use_cache); +static GSList *imap_get_uncached_msg_list + (Folder *folder, + FolderItem *item); +static gchar *imap_fetch_msg (Folder *folder, + FolderItem *item, + gint uid); +static MsgInfo *imap_get_msginfo (Folder *folder, + FolderItem *item, + gint uid); +static gint imap_add_msg (Folder *folder, + FolderItem *dest, + const gchar *file, + MsgFlags *flags, + gboolean remove_source); +static gint imap_add_msgs (Folder *folder, + FolderItem *dest, + GSList *file_list, + gboolean remove_source, + gint *first); +static gint imap_add_msg_msginfo (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo, + gboolean remove_source); +static gint imap_add_msgs_msginfo (Folder *folder, + FolderItem *dest, + GSList *msglist, + gboolean remove_source, + gint *first); + +static gint imap_move_msg (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo); +static gint imap_move_msgs (Folder *folder, + FolderItem *dest, + GSList *msglist); +static gint imap_copy_msg (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo); +static gint imap_copy_msgs (Folder *folder, + FolderItem *dest, + GSList *msglist); + +static gint imap_remove_msg (Folder *folder, + FolderItem *item, + MsgInfo *msginfo); +static gint imap_remove_msgs (Folder *folder, + FolderItem *item, + GSList *msglist); +static gint imap_remove_all_msg (Folder *folder, + FolderItem *item); + +static gboolean imap_is_msg_changed (Folder *folder, + FolderItem *item, + MsgInfo *msginfo); + +static gint imap_close (Folder *folder, + FolderItem *item); + +static gint imap_scan_folder (Folder *folder, + FolderItem *item); +static gint imap_scan_tree (Folder *folder); + +static gint imap_create_tree (Folder *folder); + +static FolderItem *imap_create_folder (Folder *folder, + FolderItem *parent, + const gchar *name); +static gint imap_rename_folder (Folder *folder, + FolderItem *item, + const gchar *name); +static gint imap_move_folder (Folder *folder, + FolderItem *item, + FolderItem *new_parent); +static gint imap_remove_folder (Folder *folder, + FolderItem *item); + +static IMAPSession *imap_session_get (Folder *folder); + +static gint imap_greeting (IMAPSession *session); +static gint imap_auth (IMAPSession *session, + const gchar *user, + const gchar *pass, + IMAPAuthType type); + +static gint imap_scan_tree_recursive (IMAPSession *session, + FolderItem *item, + GSList *item_list); +static GSList *imap_get_folder_list (IMAPSession *session, + FolderItem *item); +static GSList *imap_parse_list (IMAPSession *session, + const gchar *real_path, + gchar *separator); +static GSList *imap_add_inter_folders (GSList *item_list, + const gchar *root_path); +static GSList *imap_get_part_folder_list(GSList *item_list, + FolderItem *item); + +static void imap_create_missing_folders (Folder *folder); +static FolderItem *imap_create_special_folder + (Folder *folder, + SpecialFolderItemType stype, + const gchar *name); + +static gint imap_do_copy_msgs (Folder *folder, + FolderItem *dest, + GSList *msglist, + gboolean remove_source); +static gint imap_remove_msgs_by_seq_set (Folder *folder, + FolderItem *item, + GSList *seq_list); + +static GSList *imap_get_uncached_messages (IMAPSession *session, + FolderItem *item, + guint32 first_uid, + guint32 last_uid, + gint exists, + gboolean update_count); +static void imap_delete_cached_message (FolderItem *item, + guint32 uid); +static GSList *imap_delete_cached_messages (GSList *mlist, + FolderItem *item, + guint32 first_uid, + guint32 last_uid); +static void imap_delete_all_cached_messages (FolderItem *item); + +#if USE_SSL +static SockInfo *imap_open (const gchar *server, + gushort port, + SocksInfo *socks_info, + SSLType ssl_type); +#else +static SockInfo *imap_open (const gchar *server, + gushort port, + SocksInfo *socks_info); +#endif + +static gint imap_msg_list_change_perm_flags (GSList *msglist, + MsgPermFlags flags, + gboolean is_set); +static gchar *imap_get_flag_str (IMAPFlags flags); +static gint imap_set_message_flags (IMAPSession *session, + const gchar *seq_set, + IMAPFlags flags, + gboolean is_set); +static gint imap_select (IMAPSession *session, + IMAPFolder *folder, + const gchar *path, + gint *exists, + gint *recent, + gint *unseen, + guint32 *uid_validity); +static gint imap_status (IMAPSession *session, + IMAPFolder *folder, + const gchar *path, + gint *messages, + gint *recent, + guint32 *uid_next, + guint32 *uid_validity, + gint *unseen); + +static void imap_parse_namespace (IMAPSession *session, + IMAPFolder *folder); +static void imap_get_namespace_by_list (IMAPSession *session, + IMAPFolder *folder); +static IMAPNameSpace *imap_find_namespace (IMAPFolder *folder, + const gchar *path); +static gchar imap_get_path_separator (IMAPFolder *folder, + const gchar *path); +static gchar *imap_get_real_path (IMAPFolder *folder, + const gchar *path); + +static gchar *imap_parse_atom (IMAPSession *session, + gchar *src, + gchar *dest, + gint dest_len, + GString *str); +static MsgFlags imap_parse_flags (const gchar *flag_str); +static IMAPFlags imap_parse_imap_flags (const gchar *flag_str); +static MsgInfo *imap_parse_envelope (IMAPSession *session, + FolderItem *item, + GString *line_str); + +static gboolean imap_has_capability (IMAPSession *session, + const gchar *capability); +static void imap_capability_free (IMAPSession *session); + +/* low-level IMAP4rev1 commands */ +static gint imap_cmd_capability (IMAPSession *session); +static gint imap_cmd_authenticate + (IMAPSession *session, + const gchar *user, + const gchar *pass, + IMAPAuthType type); +static gint imap_cmd_login (IMAPSession *session, + const gchar *user, + const gchar *pass); +static gint imap_cmd_logout (IMAPSession *session); +static gint imap_cmd_noop (IMAPSession *session); +#if USE_SSL +static gint imap_cmd_starttls (IMAPSession *session); +#endif +static gint imap_cmd_namespace (IMAPSession *session, + gchar **ns_str); +static gint imap_cmd_list (IMAPSession *session, + const gchar *ref, + const gchar *mailbox, + GPtrArray *argbuf); +static gint imap_cmd_do_select (IMAPSession *session, + const gchar *folder, + gboolean examine, + gint *exists, + gint *recent, + gint *unseen, + guint32 *uid_validity); +static gint imap_cmd_select (IMAPSession *session, + const gchar *folder, + gint *exists, + gint *recent, + gint *unseen, + guint32 *uid_validity); +static gint imap_cmd_examine (IMAPSession *session, + const gchar *folder, + gint *exists, + gint *recent, + gint *unseen, + guint32 *uid_validity); +static gint imap_cmd_create (IMAPSession *session, + const gchar *folder); +static gint imap_cmd_rename (IMAPSession *session, + const gchar *oldfolder, + const gchar *newfolder); +static gint imap_cmd_delete (IMAPSession *session, + const gchar *folder); +static gint imap_cmd_subscribe (IMAPSession *session, + const gchar *folder); +static gint imap_cmd_envelope (IMAPSession *session, + const gchar *seq_set); +static gint imap_cmd_search (IMAPSession *session, + const gchar *criteria, + GArray **result); +static gint imap_cmd_fetch (IMAPSession *session, + guint32 uid, + const gchar *filename); +static gint imap_cmd_append (IMAPSession *session, + const gchar *destfolder, + const gchar *file, + IMAPFlags flags, + guint32 *new_uid); +static gint imap_cmd_copy (IMAPSession *session, + const gchar *seq_set, + const gchar *destfolder); +static gint imap_cmd_store (IMAPSession *session, + const gchar *seq_set, + const gchar *sub_cmd); +static gint imap_cmd_expunge (IMAPSession *session); +static gint imap_cmd_close (IMAPSession *session); + +static gint imap_cmd_ok (IMAPSession *session, + GPtrArray *argbuf); +static gint imap_cmd_ok_real (IMAPSession *session, + GPtrArray *argbuf); +static gint imap_cmd_gen_send (IMAPSession *session, + const gchar *format, ...); +static gint imap_cmd_gen_recv (IMAPSession *session, + gchar **ret); + +static gint imap_cmd_gen_recv_silent (IMAPSession *session, + gchar **ret); + +/* misc utility functions */ +static gchar *strchr_cpy (const gchar *src, + gchar ch, + gchar *dest, + gint len); +static gchar *get_quoted (const gchar *src, + gchar ch, + gchar *dest, + gint len); +static gchar *search_array_contain_str (GPtrArray *array, + gchar *str); +static gchar *search_array_str (GPtrArray *array, + gchar *str); +static void imap_path_separator_subst (gchar *str, + gchar separator); + +static gchar *imap_modified_utf7_to_utf8 (const gchar *mutf7_str); +static gchar *imap_utf8_to_modified_utf7 (const gchar *from); + +static GSList *imap_get_seq_set_from_msglist (GSList *msglist, + gint limit); +static gint imap_seq_set_get_count (const gchar *seq_set); +static void imap_seq_set_free (GSList *seq_list); + +static GHashTable *imap_get_uid_table (GArray *array); + +static gboolean imap_rename_folder_func (GNode *node, + gpointer data); + +#if USE_THREADS +static gint imap_thread_run (IMAPSession *session, + IMAPThreadFunc func, + gpointer data); +static gint imap_thread_run_progress (IMAPSession *session, + IMAPThreadFunc func, + IMAPProgressFunc progress_func, + gpointer data); +#endif + +static FolderClass imap_class = +{ + F_IMAP, + + imap_folder_new, + imap_folder_destroy, + + imap_scan_tree, + imap_create_tree, + + imap_get_msg_list, + imap_get_uncached_msg_list, + imap_fetch_msg, + imap_get_msginfo, + imap_add_msg, + imap_add_msgs, + imap_add_msg_msginfo, + imap_add_msgs_msginfo, + imap_move_msg, + imap_move_msgs, + imap_copy_msg, + imap_copy_msgs, + imap_remove_msg, + imap_remove_msgs, + imap_remove_all_msg, + imap_is_msg_changed, + imap_close, + imap_scan_folder, + + imap_create_folder, + imap_rename_folder, + imap_move_folder, + imap_remove_folder +}; + + +FolderClass *imap_get_class(void) +{ + return &imap_class; +} + +static Folder *imap_folder_new(const gchar *name, const gchar *path) +{ + Folder *folder; + + folder = (Folder *)g_new0(IMAPFolder, 1); + imap_folder_init(folder, name, path); + + return folder; +} + +static void imap_folder_destroy(Folder *folder) +{ + g_return_if_fail(folder->account != NULL); + + if (REMOTE_FOLDER(folder)->remove_cache_on_destroy) { + gchar *dir; + gchar *server; + + dir = folder_get_path(folder); + if (is_dir_exist(dir)) + remove_dir_recursive(dir); + g_free(dir); + + server = uriencode_for_filename(folder->account->recv_server); + dir = g_strconcat(get_imap_cache_dir(), G_DIR_SEPARATOR_S, + server, NULL); + if (is_dir_exist(dir)) + g_rmdir(dir); + g_free(dir); + g_free(server); + } + + folder_remote_folder_destroy(REMOTE_FOLDER(folder)); +} + +static void imap_folder_init(Folder *folder, const gchar *name, + const gchar *path) +{ + folder->klass = imap_get_class(); + folder_remote_folder_init(folder, name, path); +} + +static IMAPSession *imap_session_get(Folder *folder) +{ + RemoteFolder *rfolder = REMOTE_FOLDER(folder); + gint ret; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, NULL); + g_return_val_if_fail(folder->account != NULL, NULL); + + if (!prefs_common.online_mode) + return NULL; + + if (!rfolder->session) { + rfolder->session = imap_session_new(folder->account); + if (rfolder->session) + imap_parse_namespace(IMAP_SESSION(rfolder->session), + IMAP_FOLDER(folder)); + return IMAP_SESSION(rfolder->session); + } + + if (imap_is_session_active(IMAP_FOLDER(folder))) { + g_warning("imap_session_get: session is busy."); + return NULL; + } + + if (time(NULL) - rfolder->session->last_access_time < + SESSION_TIMEOUT_INTERVAL) { + return IMAP_SESSION(rfolder->session); + } + + if ((ret = imap_cmd_noop(IMAP_SESSION(rfolder->session))) != IMAP_SUCCESS) { + if (ret == IMAP_EAGAIN) { + g_warning("imap_session_get: session is busy."); + return NULL; + } + + log_warning(_("IMAP4 connection to %s has been" + " disconnected. Reconnecting...\n"), + folder->account->recv_server); + if (imap_session_reconnect(IMAP_SESSION(rfolder->session)) + == IMAP_SUCCESS) + imap_parse_namespace(IMAP_SESSION(rfolder->session), + IMAP_FOLDER(folder)); + else { + session_destroy(rfolder->session); + rfolder->session = NULL; + } + } + + return IMAP_SESSION(rfolder->session); +} + +static gint imap_greeting(IMAPSession *session) +{ + gchar *greeting; + gint ok; + + if ((ok = imap_cmd_gen_recv(session, &greeting)) != IMAP_SUCCESS) { + log_warning("Cannot get greeting message (%d)\n", ok); + return ok; + } + + if (greeting[0] != '*' || greeting[1] != ' ') + ok = IMAP_ERROR; + else if (!strncmp(greeting + 2, "OK", 2)) + ok = IMAP_SUCCESS; + else if (!strncmp(greeting + 2, "PREAUTH", 7)) { + session->authenticated = TRUE; + ok = IMAP_SUCCESS; + } else + ok = IMAP_ERROR; + + g_free(greeting); + return ok; +} + +static gint imap_auth(IMAPSession *session, const gchar *user, + const gchar *pass, IMAPAuthType type) +{ + gboolean nologin; + gint ok = IMAP_AUTHFAIL; + + nologin = imap_has_capability(session, "LOGINDISABLED"); + + switch (type) { + case 0: + if (imap_has_capability(session, "AUTH=CRAM-MD5")) + ok = imap_cmd_authenticate(session, user, pass, + IMAP_AUTH_CRAM_MD5); + else if (imap_has_capability(session, "AUTH=PLAIN")) + ok = imap_cmd_authenticate(session, user, pass, + IMAP_AUTH_PLAIN); + else if (nologin) + log_print(_("IMAP4 server disables LOGIN.\n")); + else + ok = imap_cmd_login(session, user, pass); + break; + case IMAP_AUTH_LOGIN: + if (nologin) + log_warning(_("IMAP4 server disables LOGIN.\n")); + else + ok = imap_cmd_login(session, user, pass); + break; + case IMAP_AUTH_CRAM_MD5: + case IMAP_AUTH_PLAIN: + ok = imap_cmd_authenticate(session, user, pass, type); + break; + default: + break; + } + + if (ok == IMAP_SUCCESS) + session->authenticated = TRUE; + + return ok; +} + +static Session *imap_session_new(PrefsAccount *account) +{ + IMAPSession *session; + gushort port; + + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail(account->recv_server != NULL, NULL); + g_return_val_if_fail(account->userid != NULL, NULL); + +#if USE_SSL + port = account->set_imapport ? account->imapport + : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT; +#else + port = account->set_imapport ? account->imapport : IMAP4_PORT; +#endif + + session = IMAP_SESSION(g_new0(IMAPRealSession, 1)); + + session_init(SESSION(session)); + + SESSION(session)->type = SESSION_IMAP; + SESSION(session)->sock = NULL; + SESSION(session)->server = g_strdup(account->recv_server); + SESSION(session)->port = port; +#if USE_SSL + SESSION(session)->ssl_type = account->ssl_imap; +#endif + SESSION(session)->last_access_time = time(NULL); + SESSION(session)->data = account; + + SESSION(session)->destroy = imap_session_destroy; + + session->authenticated = FALSE; + session->capability = NULL; + session->uidplus = FALSE; + session->mbox = NULL; + session->cmd_count = 0; + + session_list = g_list_append(session_list, session); + + if (imap_session_connect(session) != IMAP_SUCCESS) { + log_warning(_("Could not establish IMAP connection.\n")); + session_destroy(SESSION(session)); + return NULL; + } + + return SESSION(session); +} + +static gint imap_session_connect(IMAPSession *session) +{ + SockInfo *sock; + SocksInfo *socks_info = NULL; + PrefsAccount *account; + const gchar *pass; + + g_return_val_if_fail(session != NULL, IMAP_ERROR); + + account = (PrefsAccount *)(SESSION(session)->data); + + log_message(_("creating IMAP4 connection to %s:%d ...\n"), + SESSION(session)->server, SESSION(session)->port); + + pass = account->passwd; + if (!pass) + pass = account->tmp_pass; + if (!pass) { + gchar *tmp_pass; + + tmp_pass = input_query_password(account->recv_server, + account->userid); + if (!tmp_pass) + return IMAP_ERROR; + + account->tmp_pass = tmp_pass; + pass = account->tmp_pass; + } + + if (account->use_socks && account->use_socks_for_recv && + account->proxy_host) { + socks_info = socks_info_new(account->socks_type, account->proxy_host, account->proxy_port, account->use_proxy_auth ? account->proxy_name : NULL, account->use_proxy_auth ? account->proxy_pass : NULL); + } + +#if USE_SSL + if ((sock = imap_open(SESSION(session)->server, SESSION(session)->port, + socks_info, SESSION(session)->ssl_type)) == NULL) +#else + if ((sock = imap_open(SESSION(session)->server, SESSION(session)->port, + socks_info)) + == NULL) +#endif + return IMAP_ERROR; + + if (socks_info) + socks_info_free(socks_info); + + SESSION(session)->sock = sock; + + if (imap_greeting(session) != IMAP_SUCCESS) + return IMAP_ERROR; + if (imap_cmd_capability(session) != IMAP_SUCCESS) + return IMAP_ERROR; + + if (imap_has_capability(session, "UIDPLUS")) + session->uidplus = TRUE; + +#if USE_SSL + if (account->ssl_imap == SSL_STARTTLS && + imap_has_capability(session, "STARTTLS")) { + gint ok; + + ok = imap_cmd_starttls(session); + if (ok != IMAP_SUCCESS) { + log_warning(_("Can't start TLS session.\n")); + return IMAP_ERROR; + } + if (!ssl_init_socket_with_method(sock, SSL_METHOD_TLSv1)) + return IMAP_SOCKET; + + /* capability can be changed after STARTTLS */ + if (imap_cmd_capability(session) != IMAP_SUCCESS) + return IMAP_ERROR; + } +#endif + + if (!session->authenticated && + imap_auth(session, account->userid, pass, account->imap_auth_type) + != IMAP_SUCCESS) { + if (account->tmp_pass) { + g_free(account->tmp_pass); + account->tmp_pass = NULL; + } + imap_cmd_logout(session); + return IMAP_AUTHFAIL; + } + + return IMAP_SUCCESS; +} + +static gint imap_session_reconnect(IMAPSession *session) +{ + g_return_val_if_fail(session != NULL, IMAP_ERROR); + + session_disconnect(SESSION(session)); + + imap_capability_free(session); + session->uidplus = FALSE; + g_free(session->mbox); + session->mbox = NULL; + session->authenticated = FALSE; + SESSION(session)->state = SESSION_READY; + + return imap_session_connect(session); +} + +static void imap_session_destroy(Session *session) +{ +#if USE_THREADS + IMAPRealSession *real = (IMAPRealSession *)session; + + if (real->pool) + g_thread_pool_free(real->pool, TRUE, TRUE); +#endif + imap_capability_free(IMAP_SESSION(session)); + g_free(IMAP_SESSION(session)->mbox); + session_list = g_list_remove(session_list, session); +} + +#if 0 +static void imap_session_destroy_all(void) +{ + while (session_list != NULL) { + IMAPSession *session = (IMAPSession *)session_list->data; + + imap_cmd_logout(session); + session_destroy(SESSION(session)); + } +} +#endif + +#define THROW goto catch + +static gint imap_search_flags(IMAPSession *session, GArray **uids, + GHashTable **flags_table) +{ + gint ok; + gint i; + GArray *flag_uids; + GHashTable *unseen_table; + GHashTable *flagged_table; + GHashTable *answered_table; + guint32 uid; + IMAPFlags flags; + + ok = imap_cmd_search(session, "ALL", uids); + if (ok != IMAP_SUCCESS) return ok; + + ok = imap_cmd_search(session, "UNSEEN", &flag_uids); + if (ok != IMAP_SUCCESS) { + g_array_free(*uids, TRUE); + return ok; + } + unseen_table = imap_get_uid_table(flag_uids); + g_array_free(flag_uids, TRUE); + ok = imap_cmd_search(session, "FLAGGED", &flag_uids); + if (ok != IMAP_SUCCESS) { + g_hash_table_destroy(unseen_table); + g_array_free(*uids, TRUE); + return ok; + } + flagged_table = imap_get_uid_table(flag_uids); + g_array_free(flag_uids, TRUE); + ok = imap_cmd_search(session, "ANSWERED", &flag_uids); + if (ok != IMAP_SUCCESS) { + g_hash_table_destroy(flagged_table); + g_hash_table_destroy(unseen_table); + g_array_free(*uids, TRUE); + return ok; + } + answered_table = imap_get_uid_table(flag_uids); + g_array_free(flag_uids, TRUE); + + *flags_table = g_hash_table_new(NULL, g_direct_equal); + + for (i = 0; i < (*uids)->len; i++) { + uid = g_array_index(*uids, guint32, i); + flags = IMAP_FLAG_DRAFT; + if (!g_hash_table_lookup(unseen_table, GUINT_TO_POINTER(uid))) + flags |= IMAP_FLAG_SEEN; + if (g_hash_table_lookup(flagged_table, GUINT_TO_POINTER(uid))) + flags |= IMAP_FLAG_FLAGGED; + if (g_hash_table_lookup(answered_table, GUINT_TO_POINTER(uid))) + flags |= IMAP_FLAG_ANSWERED; + g_hash_table_insert(*flags_table, GUINT_TO_POINTER(uid), + GINT_TO_POINTER(flags)); + } + + g_hash_table_destroy(answered_table); + g_hash_table_destroy(flagged_table); + g_hash_table_destroy(unseen_table); + + return IMAP_SUCCESS; +} + +static gint imap_fetch_flags(IMAPSession *session, GArray **uids, + GHashTable **flags_table) +{ + gint ok; + gchar *tmp; + gchar *cur_pos; + gchar buf[IMAPBUFSIZE]; + guint32 uid; + IMAPFlags flags; + + if (imap_cmd_gen_send(session, "UID FETCH 1:* (UID FLAGS)") != IMAP_SUCCESS) + return IMAP_ERROR; + + *uids = g_array_new(FALSE, FALSE, sizeof(guint32)); + *flags_table = g_hash_table_new(NULL, g_direct_equal); + + log_print("IMAP4< %s\n", _("(retrieving FLAGS...)")); + + while ((ok = imap_cmd_gen_recv_silent(session, &tmp)) == IMAP_SUCCESS) { + if (tmp[0] != '*' || tmp[1] != ' ') { + log_print("IMAP4< %s\n", tmp); + g_free(tmp); + break; + } + cur_pos = tmp + 2; + +#define PARSE_ONE_ELEMENT(ch) \ +{ \ + cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \ + if (cur_pos == NULL) { \ + g_warning("cur_pos == NULL\n"); \ + g_free(tmp); \ + g_hash_table_destroy(*flags_table); \ + g_array_free(*uids, TRUE); \ + return IMAP_ERROR; \ + } \ +} + + PARSE_ONE_ELEMENT(' '); + PARSE_ONE_ELEMENT(' '); + if (strcmp(buf, "FETCH") != 0) { + g_free(tmp); + continue; + } + if (*cur_pos != '(') { + g_free(tmp); + continue; + } + cur_pos++; + uid = 0; + flags = 0; + + while (*cur_pos != '\0' && *cur_pos != ')') { + while (*cur_pos == ' ') cur_pos++; + + if (!strncmp(cur_pos, "UID ", 4)) { + cur_pos += 4; + uid = strtoul(cur_pos, &cur_pos, 10); + } else if (!strncmp(cur_pos, "FLAGS ", 6)) { + cur_pos += 6; + if (*cur_pos != '(') { + g_warning("*cur_pos != '('\n"); + break; + } + cur_pos++; + PARSE_ONE_ELEMENT(')'); + flags = imap_parse_imap_flags(buf); + flags |= IMAP_FLAG_DRAFT; + } else { + g_warning("invalid FETCH response: %s\n", cur_pos); + break; + } + } + +#undef PARSE_ONE_ELEMENT + + if (uid > 0) { + g_array_append_val(*uids, uid); + g_hash_table_insert(*flags_table, GUINT_TO_POINTER(uid), + GINT_TO_POINTER(flags)); + } + + g_free(tmp); + } + + if (ok != IMAP_SUCCESS) { + g_hash_table_destroy(*flags_table); + g_array_free(*uids, TRUE); + } + + return ok; +} + +static GSList *imap_get_msg_list_full(Folder *folder, FolderItem *item, + gboolean use_cache, + gboolean uncached_only) +{ + GSList *mlist = NULL; + IMAPSession *session; + gint ok, exists = 0, recent = 0, unseen = 0; + guint32 uid_validity = 0; + guint32 first_uid = 0, last_uid = 0; + GSList *newlist = NULL; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, NULL); + g_return_val_if_fail(folder->account != NULL, NULL); + + item->new = item->unread = item->total = 0; + + session = imap_session_get(folder); + + if (!session) { + if (uncached_only) + return NULL; + mlist = procmsg_read_cache(item, FALSE); + item->last_num = procmsg_get_last_num_in_msg_list(mlist); + procmsg_set_flags(mlist, item); + return mlist; + } + + ok = imap_select(session, IMAP_FOLDER(folder), item->path, + &exists, &recent, &unseen, &uid_validity); + if (ok != IMAP_SUCCESS) THROW; + + if (exists == 0) { + imap_delete_all_cached_messages(item); + return NULL; + } + + /* invalidate current cache if UIDVALIDITY has been changed */ + if (item->mtime != uid_validity) { + debug_print("imap_get_msg_list: " + "UIDVALIDITY has been changed.\n"); + use_cache = FALSE; + } + + if (use_cache) { + GArray *uids; + GHashTable *msg_table; + GHashTable *flags_table; + guint32 cache_last; + guint32 begin = 0; + GSList *cur, *next = NULL; + MsgInfo *msginfo; + IMAPFlags imap_flags; + guint color; + + /* get cache data */ + mlist = procmsg_read_cache(item, FALSE); + procmsg_set_flags(mlist, item); + cache_last = procmsg_get_last_num_in_msg_list(mlist); + + /* get all UID list and flags */ +#if 0 + ok = imap_search_flags(session, &uids, &flags_table); + if (ok != IMAP_SUCCESS) { + if (ok == IMAP_SOCKET || ok == IMAP_IOERR) THROW; + ok = imap_fetch_flags(session, &uids, &flags_table); + if (ok != IMAP_SUCCESS) THROW; + } +#else + ok = imap_fetch_flags(session, &uids, &flags_table); + if (ok != IMAP_SUCCESS) THROW; +#endif + + if (uids->len > 0) { + first_uid = g_array_index(uids, guint32, 0); + last_uid = g_array_index(uids, guint32, uids->len - 1); + } else { + g_array_free(uids, TRUE); + g_hash_table_destroy(flags_table); + THROW; + } + + /* sync message flags with server */ + for (cur = mlist; cur != NULL; cur = next) { + msginfo = (MsgInfo *)cur->data; + next = cur->next; + imap_flags = GPOINTER_TO_INT(g_hash_table_lookup + (flags_table, + GUINT_TO_POINTER(msginfo->msgnum))); + + if (imap_flags == 0) { + debug_print("imap_get_msg_list: " + "message %u has been deleted.\n", + msginfo->msgnum); + imap_delete_cached_message + (item, msginfo->msgnum); + if (MSG_IS_NEW(msginfo->flags)) + item->new--; + if (MSG_IS_UNREAD(msginfo->flags)) + item->unread--; + item->total--; + mlist = g_slist_remove(mlist, msginfo); + procmsg_msginfo_free(msginfo); + item->cache_dirty = TRUE; + item->mark_dirty = TRUE; + continue; + } + + if (!IMAP_IS_SEEN(imap_flags)) { + if (!MSG_IS_UNREAD(msginfo->flags)) { + item->unread++; + MSG_SET_PERM_FLAGS(msginfo->flags, + MSG_UNREAD); + item->mark_dirty = TRUE; + } + } else { + if (MSG_IS_NEW(msginfo->flags)) { + item->new--; + item->mark_dirty = TRUE; + } + if (MSG_IS_UNREAD(msginfo->flags)) { + item->unread--; + item->mark_dirty = TRUE; + } + MSG_UNSET_PERM_FLAGS(msginfo->flags, + MSG_NEW|MSG_UNREAD); + } + + if (IMAP_IS_FLAGGED(imap_flags)) { + if (!MSG_IS_MARKED(msginfo->flags)) { + MSG_SET_PERM_FLAGS(msginfo->flags, + MSG_MARKED); + item->mark_dirty = TRUE; + } + } else { + if (MSG_IS_MARKED(msginfo->flags)) { + MSG_UNSET_PERM_FLAGS(msginfo->flags, + MSG_MARKED); + item->mark_dirty = TRUE; + } + } + if (IMAP_IS_ANSWERED(imap_flags)) { + if (!MSG_IS_REPLIED(msginfo->flags)) { + MSG_SET_PERM_FLAGS(msginfo->flags, + MSG_REPLIED); + item->mark_dirty = TRUE; + } + } else { + if (MSG_IS_REPLIED(msginfo->flags)) { + MSG_UNSET_PERM_FLAGS(msginfo->flags, + MSG_REPLIED); + item->mark_dirty = TRUE; + } + } + + color = IMAP_GET_COLORLABEL_VALUE(imap_flags); + if (MSG_GET_COLORLABEL_VALUE(msginfo->flags) != color) { + MSG_UNSET_PERM_FLAGS(msginfo->flags, + MSG_CLABEL_FLAG_MASK); + MSG_SET_COLORLABEL_VALUE(msginfo->flags, color); + item->mark_dirty = TRUE; + } + } + + /* check for the first new message */ + msg_table = procmsg_msg_hash_table_create(mlist); + if (msg_table == NULL) + begin = first_uid; + else { + gint i; + + for (i = 0; i < uids->len; i++) { + guint32 uid; + + uid = g_array_index(uids, guint32, i); + if (g_hash_table_lookup + (msg_table, GUINT_TO_POINTER(uid)) + == NULL) { + debug_print("imap_get_msg_list: " + "first new UID: %u\n", uid); + begin = uid; + break; + } + } + g_hash_table_destroy(msg_table); + } + + g_array_free(uids, TRUE); + g_hash_table_destroy(flags_table); + + /* remove ununsed caches */ + if (first_uid > 0 && last_uid > 0) { + mlist = imap_delete_cached_messages + (mlist, item, 0, first_uid - 1); + mlist = imap_delete_cached_messages + (mlist, item, begin > 0 ? begin : last_uid + 1, + UINT_MAX); + } + + if (begin > 0 && begin <= last_uid) { + newlist = imap_get_uncached_messages + (session, item, begin, last_uid, + exists - item->total, TRUE); + if (newlist) { + item->cache_dirty = TRUE; + item->mark_dirty = TRUE; + } + mlist = g_slist_concat(mlist, newlist); + } + } else { + imap_delete_all_cached_messages(item); + mlist = imap_get_uncached_messages(session, item, 0, 0, exists, + TRUE); + last_uid = procmsg_get_last_num_in_msg_list(mlist); + item->cache_dirty = TRUE; + item->mark_dirty = TRUE; + newlist = mlist; + } + + if (!uncached_only) + mlist = procmsg_sort_msg_list(mlist, item->sort_key, + item->sort_type); + + item->last_num = last_uid; + + if (item->mark_queue) + item->mark_dirty = TRUE; + + debug_print("cache_dirty: %d, mark_dirty: %d\n", + item->cache_dirty, item->mark_dirty); + + if (!item->opened) { + item->mtime = uid_validity; + if (item->cache_dirty) + procmsg_write_cache_list(item, mlist); + if (item->mark_dirty) + procmsg_write_flags_list(item, mlist); + } + +catch: + if (uncached_only) { + GSList *cur; + + if (newlist == NULL) { + procmsg_msg_list_free(mlist); + return NULL; + } + if (mlist == newlist) + return newlist; + for (cur = mlist; cur != NULL; cur = cur->next) { + if (cur->next == newlist) { + cur->next = NULL; + procmsg_msg_list_free(mlist); + return newlist; + } + } + procmsg_msg_list_free(mlist); + return NULL; + } + + return mlist; +} + +#undef THROW + +static GSList *imap_get_msg_list(Folder *folder, FolderItem *item, + gboolean use_cache) +{ + return imap_get_msg_list_full(folder, item, use_cache, FALSE); +} + +static GSList *imap_get_uncached_msg_list(Folder *folder, FolderItem *item) +{ + return imap_get_msg_list_full(folder, item, TRUE, TRUE); +} + +static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid) +{ + gchar *path, *filename; + IMAPSession *session; + gchar nstr[16]; + guint32 uid32 = (guint32)uid; + gint ok; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + + path = folder_item_get_path(item); + if (!is_dir_exist(path)) + make_dir_hier(path); + g_snprintf(nstr, sizeof(nstr), "%u", uid32); + filename = g_strconcat(path, G_DIR_SEPARATOR_S, nstr, NULL); + g_free(path); + + if (is_file_exist(filename) && get_file_size(filename) > 0) { + debug_print("message %u has been already cached.\n", uid32); + return filename; + } + + session = imap_session_get(folder); + if (!session) { + g_free(filename); + return NULL; + } + + ok = imap_select(session, IMAP_FOLDER(folder), item->path, + NULL, NULL, NULL, NULL); + if (ok != IMAP_SUCCESS) { + g_warning("can't select mailbox %s\n", item->path); + g_free(filename); + return NULL; + } + + status_print(_("Getting message %u"), uid32); + debug_print("getting message %u...\n", uid32); + ok = imap_cmd_fetch(session, uid32, filename); + + if (ok != IMAP_SUCCESS) { + g_warning("can't fetch message %u\n", uid32); + g_free(filename); + return NULL; + } + + return filename; +} + +static MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid) +{ + IMAPSession *session; + GSList *list; + MsgInfo *msginfo = NULL; + gint ok; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + + session = imap_session_get(folder); + g_return_val_if_fail(session != NULL, NULL); + + ok = imap_select(session, IMAP_FOLDER(folder), item->path, + NULL, NULL, NULL, NULL); + if (ok != IMAP_SUCCESS) + return NULL; + + list = imap_get_uncached_messages(session, item, uid, uid, 0, FALSE); + if (list) { + msginfo = (MsgInfo *)list->data; + list->data = NULL; + } + procmsg_msg_list_free(list); + + return msginfo; +} + +static gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file, + MsgFlags *flags, gboolean remove_source) +{ + GSList file_list; + MsgFileInfo fileinfo; + + g_return_val_if_fail(file != NULL, -1); + + fileinfo.file = (gchar *)file; + fileinfo.flags = flags; + file_list.data = &fileinfo; + file_list.next = NULL; + + return imap_add_msgs(folder, dest, &file_list, remove_source, NULL); +} + +static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list, + gboolean remove_source, gint *first) +{ + gchar *destdir; + IMAPSession *session; + gint messages, recent, unseen; + guint32 uid_next, uid_validity; + guint32 last_uid = 0; + GSList *cur; + MsgFileInfo *fileinfo; + gint count = 1; + gint total; + gint ok; + GTimeVal tv_prev, tv_cur; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(file_list != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + g_get_current_time(&tv_prev); + ui_update(); + + ok = imap_status(session, IMAP_FOLDER(folder), dest->path, + &messages, &recent, &uid_next, &uid_validity, &unseen); + if (ok != IMAP_SUCCESS) { + g_warning("can't append messages\n"); + return -1; + } + + destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path); + + if (!session->uidplus) + last_uid = uid_next - 1; + if (first) + *first = uid_next; + + total = g_slist_length(file_list); + + for (cur = file_list; cur != NULL; cur = cur->next) { + IMAPFlags iflags = 0; + guint32 new_uid = 0; + + fileinfo = (MsgFileInfo *)cur->data; + + if (fileinfo->flags) { + if (MSG_IS_MARKED(*fileinfo->flags)) + iflags |= IMAP_FLAG_FLAGGED; + if (MSG_IS_REPLIED(*fileinfo->flags)) + iflags |= IMAP_FLAG_ANSWERED; + if (!MSG_IS_UNREAD(*fileinfo->flags)) + iflags |= IMAP_FLAG_SEEN; + } + + if (dest->stype == F_OUTBOX || + dest->stype == F_QUEUE || + dest->stype == F_DRAFT) + iflags |= IMAP_FLAG_SEEN; + + g_get_current_time(&tv_cur); + if (tv_cur.tv_sec > tv_prev.tv_sec || + tv_cur.tv_usec - tv_prev.tv_usec > + PROGRESS_UPDATE_INTERVAL * 1000) { + status_print(_("Appending messages to %s (%d / %d)"), + dest->path, count, total); + progress_show(count, total); + ui_update(); + tv_prev = tv_cur; + } + ++count; + + ok = imap_cmd_append(session, destdir, fileinfo->file, iflags, + &new_uid); + + if (ok != IMAP_SUCCESS) { + g_warning("can't append message %s\n", fileinfo->file); + g_free(destdir); + progress_show(0, 0); + return -1; + } + + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "add-msg", dest, fileinfo->file, new_uid); + + if (!session->uidplus) + last_uid++; + else if (last_uid < new_uid) + last_uid = new_uid; + + dest->last_num = last_uid; + dest->total++; + dest->updated = TRUE; + + if (fileinfo->flags) { + if (MSG_IS_UNREAD(*fileinfo->flags)) + dest->unread++; + } else + dest->unread++; + } + + progress_show(0, 0); + g_free(destdir); + + if (remove_source) { + for (cur = file_list; cur != NULL; cur = cur->next) { + fileinfo = (MsgFileInfo *)cur->data; + if (g_unlink(fileinfo->file) < 0) + FILE_OP_ERROR(fileinfo->file, "unlink"); + } + } + + return last_uid; +} + +static gint imap_add_msg_msginfo(Folder *folder, FolderItem *dest, + MsgInfo *msginfo, gboolean remove_source) +{ + GSList msglist; + + g_return_val_if_fail(msginfo != NULL, -1); + + msglist.data = msginfo; + msglist.next = NULL; + + return imap_add_msgs_msginfo(folder, dest, &msglist, remove_source, + NULL); +} + +static gint imap_add_msgs_msginfo(Folder *folder, FolderItem *dest, + GSList *msglist, gboolean remove_source, + gint *first) +{ + GSList *file_list; + gint ret; + + file_list = procmsg_get_message_file_list(msglist); + g_return_val_if_fail(file_list != NULL, -1); + + ret = imap_add_msgs(folder, dest, file_list, remove_source, first); + + procmsg_message_file_list_free(file_list); + + return ret; +} + +static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist, + gboolean remove_source) +{ + FolderItem *src; + gchar *destdir; + GSList *seq_list, *cur; + MsgInfo *msginfo; + IMAPSession *session; + gint count = 0, total; + gint ok = IMAP_SUCCESS; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + ui_update(); + + msginfo = (MsgInfo *)msglist->data; + + src = msginfo->folder; + if (src == dest) { + g_warning("the src folder is identical to the dest.\n"); + return -1; + } + + ok = imap_select(session, IMAP_FOLDER(folder), src->path, + NULL, NULL, NULL, NULL); + if (ok != IMAP_SUCCESS) + return ok; + + destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path); + + total = g_slist_length(msglist); + seq_list = imap_get_seq_set_from_msglist(msglist, IMAP_COPY_LIMIT); + + for (cur = seq_list; cur != NULL; cur = cur->next) { + gchar *seq_set = (gchar *)cur->data; + + count += imap_seq_set_get_count(seq_set); + + if (remove_source) { + status_print(_("Moving messages %s to %s ..."), + seq_set, dest->path); + debug_print("Moving message %s/[%s] to %s ...\n", + src->path, seq_set, dest->path); + } else { + status_print(_("Copying messages %s to %s ..."), + seq_set, dest->path); + debug_print("Copying message %s/[%s] to %s ...\n", + src->path, seq_set, dest->path); + } + progress_show(count, total); + ui_update(); + + ok = imap_cmd_copy(session, seq_set, destdir); + if (ok != IMAP_SUCCESS) { + imap_seq_set_free(seq_list); + progress_show(0, 0); + return -1; + } + } + + progress_show(0, 0); + + dest->updated = TRUE; + + imap_seq_set_free(seq_list); + g_free(destdir); + + for (cur = msglist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "add-msg", dest, NULL, 0); + + dest->total++; + if (MSG_IS_NEW(msginfo->flags)) + dest->new++; + if (MSG_IS_UNREAD(msginfo->flags)) + dest->unread++; + } + + if (remove_source) { + ok = imap_remove_msgs(folder, src, msglist); + if (ok != IMAP_SUCCESS) + return ok; + } + + if (ok == IMAP_SUCCESS) + return 0; + else + return -1; +} + +static gint imap_move_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo) +{ + GSList msglist; + + g_return_val_if_fail(msginfo != NULL, -1); + + msglist.data = msginfo; + msglist.next = NULL; + + return imap_move_msgs(folder, dest, &msglist); +} + +static gint imap_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist) +{ + MsgInfo *msginfo; + GSList *file_list; + gint ret = 0; + + 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); + + if (folder == msginfo->folder->folder) + return imap_do_copy_msgs(folder, dest, msglist, TRUE); + + file_list = procmsg_get_message_file_list(msglist); + g_return_val_if_fail(file_list != NULL, -1); + + ret = imap_add_msgs(folder, dest, file_list, FALSE, NULL); + + procmsg_message_file_list_free(file_list); + + if (ret != -1) + ret = folder_item_remove_msgs(msginfo->folder, msglist); + + return ret; +} + +static gint imap_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 imap_copy_msgs(folder, dest, &msglist); +} + +static gint imap_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist) +{ + 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); + + if (folder == msginfo->folder->folder) + return imap_do_copy_msgs(folder, dest, msglist, FALSE); + + file_list = procmsg_get_message_file_list(msglist); + g_return_val_if_fail(file_list != NULL, -1); + + ret = imap_add_msgs(folder, dest, file_list, FALSE, NULL); + + procmsg_message_file_list_free(file_list); + + return ret; +} + +static gint imap_remove_msgs_by_seq_set(Folder *folder, FolderItem *item, + GSList *seq_list) +{ + gint ok; + IMAPSession *session; + GSList *cur; + + g_return_val_if_fail(seq_list != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + for (cur = seq_list; cur != NULL; cur = cur->next) { + gchar *seq_set = (gchar *)cur->data; + + status_print(_("Removing messages %s"), seq_set); + ui_update(); + + ok = imap_set_message_flags(session, seq_set, IMAP_FLAG_DELETED, + TRUE); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't set deleted flags: %s\n"), + seq_set); + return ok; + } + } + + ok = imap_cmd_expunge(session); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't expunge\n")); + } else { + /* for some broken IMAP servers */ + ok = imap_cmd_noop(session); + } + + item->updated = TRUE; + + return ok; +} + +static gint imap_remove_msg(Folder *folder, FolderItem *item, MsgInfo *msginfo) +{ + GSList msglist; + + g_return_val_if_fail(msginfo != NULL, -1); + + msglist.data = msginfo; + msglist.next = NULL; + + return imap_remove_msgs(folder, item, &msglist); +} + +static gint imap_remove_msgs(Folder *folder, FolderItem *item, GSList *msglist) +{ + gint ok; + IMAPSession *session; + GSList *seq_list, *cur; + gchar *dir; + gboolean dir_exist; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1); + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + ok = imap_select(session, IMAP_FOLDER(folder), item->path, + NULL, NULL, NULL, NULL); + if (ok != IMAP_SUCCESS) + return ok; + + seq_list = imap_get_seq_set_from_msglist(msglist, 0); + ok = imap_remove_msgs_by_seq_set(folder, item, seq_list); + imap_seq_set_free(seq_list); + if (ok != IMAP_SUCCESS) + return ok; + + dir = folder_item_get_path(item); + dir_exist = is_dir_exist(dir); + for (cur = msglist; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + guint32 uid = msginfo->msgnum; + + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "remove-msg", item, NULL, uid); + + if (dir_exist) + remove_numbered_files(dir, uid, uid); + item->total--; + if (MSG_IS_NEW(msginfo->flags)) + item->new--; + if (MSG_IS_UNREAD(msginfo->flags)) + item->unread--; + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID); + } + g_free(dir); + + return IMAP_SUCCESS; +} + +static gint imap_remove_all_msg(Folder *folder, FolderItem *item) +{ + gint ok; + IMAPSession *session; + gchar *dir; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + ok = imap_select(session, IMAP_FOLDER(folder), item->path, + NULL, NULL, NULL, NULL); + if (ok != IMAP_SUCCESS) + return ok; + + status_print(_("Removing all messages in %s"), item->path); + ui_update(); + + ok = imap_cmd_gen_send(session, "STORE 1:* +FLAGS.SILENT (\\Deleted)"); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't set deleted flags: 1:*\n")); + return ok; + } + ok = imap_cmd_ok(session, NULL); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't set deleted flags: 1:*\n")); + return ok; + } + + ok = imap_cmd_expunge(session); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't expunge\n")); + return ok; + } + + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "remove-all-msg", item); + + item->new = item->unread = item->total = 0; + item->updated = TRUE; + + dir = folder_item_get_path(item); + if (is_dir_exist(dir)) + remove_all_numbered_files(dir); + g_free(dir); + + return IMAP_SUCCESS; +} + +static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item, + MsgInfo *msginfo) +{ + /* TODO: properly implement this method */ + return FALSE; +} + +static gint imap_close(Folder *folder, FolderItem *item) +{ + gint ok; + IMAPSession *session; + + g_return_val_if_fail(folder != NULL, -1); + + if (!item->path) return 0; + + if (!REMOTE_FOLDER(folder)->session) + return 0; + + session = imap_session_get(folder); + if (!session) return -1; + + if (session->mbox) { + if (strcmp2(session->mbox, item->path) != 0) return -1; + + ok = imap_cmd_close(session); + if (ok != IMAP_SUCCESS) + log_warning(_("can't close folder\n")); + + g_free(session->mbox); + session->mbox = NULL; + + return ok; + } else + return 0; +} + +static gint imap_scan_folder(Folder *folder, FolderItem *item) +{ + IMAPSession *session; + gint messages, recent, unseen; + guint32 uid_next, uid_validity; + gint ok; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + ok = imap_status(session, IMAP_FOLDER(folder), item->path, + &messages, &recent, &uid_next, &uid_validity, &unseen); + if (ok != IMAP_SUCCESS) return -1; + + item->new = unseen > 0 ? recent : 0; + item->unread = unseen; + item->total = messages; + item->last_num = (messages > 0 && uid_next > 0) ? uid_next - 1 : 0; + /* item->mtime = uid_validity; */ + item->updated = TRUE; + + return 0; +} + +static gint imap_scan_tree(Folder *folder) +{ + FolderItem *item = NULL; + IMAPSession *session; + gchar *root_folder = NULL; + GSList *item_list, *cur; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(folder->account != NULL, -1); + + session = imap_session_get(folder); + if (!session) { + if (!folder->node) { + folder_tree_destroy(folder); + item = folder_item_new(folder->name, NULL); + item->folder = folder; + folder->node = item->node = g_node_new(item); + } + return -1; + } + + if (folder->account->imap_dir && *folder->account->imap_dir) { + gchar *real_path; + GPtrArray *argbuf; + gint ok; + + Xstrdup_a(root_folder, folder->account->imap_dir, return -1); + extract_quote(root_folder, '"'); + subst_char(root_folder, + imap_get_path_separator(IMAP_FOLDER(folder), + root_folder), + '/'); + strtailchomp(root_folder, '/'); + real_path = imap_get_real_path + (IMAP_FOLDER(folder), root_folder); + debug_print("IMAP root directory: %s\n", real_path); + + /* check if root directory exist */ + argbuf = g_ptr_array_new(); + ok = imap_cmd_list(session, NULL, real_path, argbuf); + if (ok != IMAP_SUCCESS || + search_array_str(argbuf, "LIST ") == NULL) { + log_warning(_("root folder %s not exist\n"), real_path); + g_ptr_array_free(argbuf, TRUE); + g_free(real_path); + return -1; + } + g_ptr_array_free(argbuf, TRUE); + g_free(real_path); + } + + if (folder->node) + item = FOLDER_ITEM(folder->node->data); + if (!item || ((item->path || root_folder) && + strcmp2(item->path, root_folder) != 0)) { + folder_tree_destroy(folder); + item = folder_item_new(folder->name, root_folder); + item->folder = folder; + folder->node = item->node = g_node_new(item); + } + + item_list = imap_get_folder_list(session, item); + imap_scan_tree_recursive(session, item, item_list); + imap_create_missing_folders(folder); + + for (cur = item_list; cur != NULL; cur = cur->next) + folder_item_destroy(FOLDER_ITEM(cur->data)); + g_slist_free(item_list); + + return 0; +} + +static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item, + GSList *item_list) +{ + Folder *folder; + FolderItem *new_item; + GSList *part_list, *cur; + GNode *node; + + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->folder != NULL, -1); + g_return_val_if_fail(item->no_sub == FALSE, -1); + + folder = item->folder; + + part_list = imap_get_part_folder_list(item_list, item); + + node = item->node->children; + while (node != NULL) { + FolderItem *old_item = FOLDER_ITEM(node->data); + GNode *next = node->next; + + new_item = NULL; + + for (cur = part_list; cur != NULL; cur = cur->next) { + FolderItem *cur_item = FOLDER_ITEM(cur->data); + if (!strcmp2(old_item->path, cur_item->path)) { + new_item = cur_item; + break; + } + } + if (!new_item) { + if (old_item->stype != F_VIRTUAL) { + debug_print("folder '%s' not found. removing...\n", old_item->path); + folder_item_remove(old_item); + } + } else if (old_item->stype == F_VIRTUAL) { + debug_print("IMAP4 folder found at the location of virtual folder '%s'. removing virtual folder...\n", old_item->path); + virtual_get_class()->remove_folder + (folder, old_item); + } else { + old_item->no_sub = new_item->no_sub; + old_item->no_select = new_item->no_select; + if (old_item->no_select == TRUE) + old_item->new = old_item->unread = + old_item->total = 0; + if (old_item->no_sub == TRUE && node->children) { + debug_print("folder '%s' doesn't have " + "subfolders. removing...\n", + old_item->path); + folder_item_remove_children(old_item); + } + } + + node = next; + } + + for (cur = part_list; cur != NULL; cur = cur->next) { + FolderItem *cur_item = FOLDER_ITEM(cur->data); + new_item = NULL; + for (node = item->node->children; node != NULL; + node = node->next) { + if (!strcmp2(FOLDER_ITEM(node->data)->path, + cur_item->path)) { + new_item = FOLDER_ITEM(node->data); + cur_item = NULL; + break; + } + } + if (!new_item) { + new_item = folder_item_copy(cur_item); + debug_print("new folder '%s' found.\n", new_item->path); + folder_item_append(item, new_item); + } + + if (!g_ascii_strcasecmp(new_item->path, "INBOX")) { + new_item->stype = F_INBOX; + folder->inbox = new_item; + } else if (!item->parent || item->stype == F_INBOX) { + const gchar *base; + + base = g_basename(new_item->path); + + if (!folder->outbox && + !g_ascii_strcasecmp(base, "Sent")) { + new_item->stype = F_OUTBOX; + folder->outbox = new_item; + } else if (!folder->draft && + !g_ascii_strcasecmp(base, "Drafts")) { + new_item->stype = F_DRAFT; + folder->draft = new_item; + } else if (!folder->queue && + !g_ascii_strcasecmp(base, "Queue")) { + new_item->stype = F_QUEUE; + folder->queue = new_item; + } else if (!folder->trash && + !g_ascii_strcasecmp(base, "Trash")) { + new_item->stype = F_TRASH; + folder->trash = new_item; + } + } + +#if 0 + if (new_item->no_select == FALSE) + imap_scan_folder(folder, new_item); +#endif + if (new_item->no_sub == FALSE) + imap_scan_tree_recursive(session, new_item, item_list); + } + + g_slist_free(part_list); + + return IMAP_SUCCESS; +} + +static GSList *imap_get_folder_list(IMAPSession *session, FolderItem *item) +{ + Folder *folder; + IMAPFolder *imapfolder; + gchar *real_path; + gchar *wildcard_path; + gchar separator; + GSList *item_list = NULL; + + folder = item->folder; + imapfolder = IMAP_FOLDER(folder); + + separator = imap_get_path_separator(imapfolder, item->path); + + if (folder->ui_func) + folder->ui_func(folder, item, folder->ui_func_data); + + if (item->path) { + real_path = imap_get_real_path(imapfolder, item->path); + strtailchomp(real_path, separator); + wildcard_path = g_strdup_printf("%s%c*", real_path, separator); + } else { + real_path = g_strdup(""); + wildcard_path = g_strdup("*"); + } + + if (imap_cmd_gen_send(session, "LIST \"\" \"%s\"", wildcard_path) == IMAP_SUCCESS) { + item_list = imap_parse_list(session, real_path, NULL); + item_list = imap_add_inter_folders(item_list, item->path); + } + g_free(real_path); + g_free(wildcard_path); + + return item_list; +} + +static GSList *imap_parse_list(IMAPSession *session, const gchar *real_path, + gchar *separator) +{ + gchar buf[IMAPBUFSIZE]; + gchar flags[256]; + gchar separator_str[16]; + gchar *p; + const gchar *name; + gchar *loc_name, *loc_path; + GSList *item_list = NULL; + GString *str; + FolderItem *new_item; + + debug_print("getting list of %s ...\n", + *real_path ? real_path : "\"\""); + + str = g_string_new(NULL); + + for (;;) { + if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) { + log_warning(_("error occurred while getting LIST.\n")); + break; + } + strretchomp(buf); + if (buf[0] != '*' || buf[1] != ' ') { + log_print("IMAP4< %s\n", buf); + if (sscanf(buf, "%*d %16s", buf) < 1 || + strcmp(buf, "OK") != 0) + log_warning(_("error occurred while getting LIST.\n")); + + break; + } + debug_print("IMAP4< %s\n", buf); + + g_string_assign(str, buf); + p = str->str + 2; + if (strncmp(p, "LIST ", 5) != 0) continue; + p += 5; + + if (*p != '(') continue; + p++; + p = strchr_cpy(p, ')', flags, sizeof(flags)); + if (!p) continue; + while (*p == ' ') p++; + + p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str)); + if (!p) continue; + extract_quote(separator_str, '"'); + if (!strcmp(separator_str, "NIL")) + separator_str[0] = '\0'; + if (separator) + *separator = separator_str[0]; + + buf[0] = '\0'; + while (*p == ' ') p++; + if ((*p == '~' && *(p + 1) == '{') || *p == '{' || *p == '"') + p = imap_parse_atom(session, p, buf, sizeof(buf), str); + else + strncpy2(buf, p, sizeof(buf)); + strtailchomp(buf, separator_str[0]); + if (buf[0] == '\0') continue; + if (!strcmp(buf, real_path)) continue; + + if (separator_str[0] != '\0') + subst_char(buf, separator_str[0], '/'); + name = g_basename(buf); + if (name[0] == '.') continue; + + loc_name = imap_modified_utf7_to_utf8(name); + loc_path = imap_modified_utf7_to_utf8(buf); + new_item = folder_item_new(loc_name, loc_path); + if (strcasestr(flags, "\\Noinferiors") != NULL) + new_item->no_sub = TRUE; + if (g_ascii_strcasecmp(buf, "INBOX") != 0 && + strcasestr(flags, "\\Noselect") != NULL) + new_item->no_select = TRUE; + + item_list = g_slist_prepend(item_list, new_item); + + debug_print("folder '%s' found.\n", loc_path); + g_free(loc_path); + g_free(loc_name); + } + + g_string_free(str, TRUE); + + item_list = g_slist_reverse(item_list); + return item_list; +} + +static GSList *imap_add_inter_folders(GSList *item_list, const gchar *root_path) +{ + FolderItem *item; + GSList *cur; + GSList *add_list = NULL; + GHashTable *exist; + const gchar *p; + gint root_path_len = 0; + + if (root_path) + root_path_len = strlen(root_path); + + exist = g_hash_table_new(g_str_hash, g_str_equal); + + for (cur = item_list; cur != NULL; cur = cur->next) { + item = FOLDER_ITEM(cur->data); + + if (root_path_len > 0 && + strncmp(root_path, item->path, root_path_len) != 0) + continue; + p = item->path + root_path_len; + if (root_path_len > 0 && *p != '/') continue; + while (*p == '/') p++; + if (*p == '\0') continue; + g_hash_table_insert(exist, (gpointer)p, GINT_TO_POINTER(1)); + } + + for (cur = item_list; cur != NULL; cur = cur->next) { + const gchar *q, *r; + gchar *parent, *full_parent; + FolderItem *new_item; + + item = FOLDER_ITEM(cur->data); + + if (root_path_len > 0 && + strncmp(root_path, item->path, root_path_len) != 0) + continue; + p = item->path + root_path_len; + if (root_path_len > 0 && *p != '/') continue; + while (*p == '/') p++; + if (*p == '\0') continue; + + q = p; + while ((q = strchr(q, '/')) != NULL) { + parent = g_strndup(p, q - p); + if (!g_hash_table_lookup(exist, parent)) { + if (root_path_len > 0) + full_parent = g_strconcat + (root_path, "/", parent, NULL); + else + full_parent = g_strdup(parent); + new_item = folder_item_new(g_basename(parent), + full_parent); + new_item->no_select = TRUE; + add_list = g_slist_prepend(add_list, new_item); + r = new_item->path + root_path_len; + while (*r == '/') r++; + g_hash_table_insert(exist, (gpointer)r, + GINT_TO_POINTER(1)); + debug_print("intermediate folder '%s' added\n", + full_parent); + g_free(full_parent); + } + g_free(parent); + while (*q == '/') q++; + } + } + + g_hash_table_destroy(exist); + + add_list = g_slist_reverse(add_list); + item_list = g_slist_concat(item_list, add_list); + + return item_list; +} + +static GSList *imap_get_part_folder_list(GSList *item_list, FolderItem *item) +{ + FolderItem *cur_item; + GSList *part_list = NULL, *cur; + gint len; + + if (!item->path) { + debug_print("imap_get_part_folder_list(): get root folders\n"); + for (cur = item_list; cur != NULL; cur = cur->next) { + cur_item = FOLDER_ITEM(cur->data); + + if (!strchr(cur_item->path, '/')) { + part_list = g_slist_prepend(part_list, + cur_item); + debug_print("append '%s'\n", cur_item->path); + } + } + part_list = g_slist_reverse(part_list); + return part_list; + } + + len = strlen(item->path); + debug_print("imap_get_part_folder_list(): get folders under '%s'\n", + item->path); + + for (cur = item_list; cur != NULL; cur = cur->next) { + cur_item = FOLDER_ITEM(cur->data); + + if (!strncmp(cur_item->path, item->path, len) && + cur_item->path[len] == '/' && + !strchr(cur_item->path + len + 1, '/')) { + part_list = g_slist_prepend(part_list, cur_item); + debug_print("append '%s'\n", cur_item->path); + } + } + + part_list = g_slist_reverse(part_list); + return part_list; +} + +static gint imap_create_tree(Folder *folder) +{ + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(folder->node != NULL, -1); + g_return_val_if_fail(folder->node->data != NULL, -1); + g_return_val_if_fail(folder->account != NULL, -1); + + if (imap_scan_tree(folder) < 0) + return -1; + imap_create_missing_folders(folder); + + return 0; +} + +static void imap_create_missing_folders(Folder *folder) +{ + g_return_if_fail(folder != NULL); + + if (!folder->inbox) + folder->inbox = imap_create_special_folder + (folder, F_INBOX, "INBOX"); +#if 0 + if (!folder->outbox) + folder->outbox = imap_create_special_folder + (folder, F_OUTBOX, "Sent"); + if (!folder->draft) + folder->draft = imap_create_special_folder + (folder, F_DRAFT, "Drafts"); + if (!folder->queue) + folder->queue = imap_create_special_folder + (folder, F_QUEUE, "Queue"); +#endif + if (!folder->trash) + folder->trash = imap_create_special_folder + (folder, F_TRASH, "Trash"); +} + +static FolderItem *imap_create_special_folder(Folder *folder, + SpecialFolderItemType stype, + const gchar *name) +{ + FolderItem *item; + FolderItem *new_item; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(folder->node != NULL, NULL); + g_return_val_if_fail(folder->node->data != NULL, NULL); + g_return_val_if_fail(folder->account != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + item = FOLDER_ITEM(folder->node->data); + new_item = imap_create_folder(folder, item, name); + + if (!new_item) { + g_warning(_("Can't create '%s'\n"), name); + if (!folder->inbox) return NULL; + + new_item = imap_create_folder(folder, folder->inbox, name); + if (!new_item) + g_warning(_("Can't create '%s' under INBOX\n"), name); + else + new_item->stype = stype; + } else + new_item->stype = stype; + + return new_item; +} + +static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent, + const gchar *name) +{ + gchar *dirpath, *imap_path; + IMAPSession *session; + FolderItem *new_item; + gchar separator; + gchar *new_name; + const gchar *p; + gboolean no_sub = FALSE, no_select = FALSE; + gint ok; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(folder->account != NULL, NULL); + g_return_val_if_fail(parent != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + session = imap_session_get(folder); + if (!session) return NULL; + + if (!parent->parent && g_ascii_strcasecmp(name, "INBOX") == 0) + dirpath = g_strdup(name); + else if (parent->path) + dirpath = g_strconcat(parent->path, "/", name, NULL); + else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0') + dirpath = g_strdup(name); + else if (folder->account->imap_dir && *folder->account->imap_dir) { + gchar *imap_dir; + + Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL); + strtailchomp(imap_dir, '/'); + dirpath = g_strconcat(imap_dir, "/", name, NULL); + } else + dirpath = g_strdup(name); + + /* keep trailing directory separator to create a folder that contains + sub folder */ + imap_path = imap_utf8_to_modified_utf7(dirpath); + strtailchomp(dirpath, '/'); + Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;}); + strtailchomp(new_name, '/'); + separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path); + imap_path_separator_subst(imap_path, separator); + subst_char(new_name, '/', separator); + + if (g_ascii_strcasecmp(name, "INBOX") != 0) { + GPtrArray *argbuf; + gint i; + gboolean exist = FALSE; + + argbuf = g_ptr_array_new(); + ok = imap_cmd_list(session, NULL, imap_path, argbuf); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't create mailbox: LIST failed\n")); + g_free(imap_path); + g_free(dirpath); + g_ptr_array_free(argbuf, TRUE); + return NULL; + } + + for (i = 0; i < argbuf->len; i++) { + gchar *str; + str = g_ptr_array_index(argbuf, i); + if (!strncmp(str, "LIST ", 5)) { + exist = TRUE; + if (strcasestr(str + 5, "\\Noinferiors")) + no_sub = TRUE; + if (strcasestr(str + 5, "\\Noselect")) + no_select = TRUE; + break; + } + } + + g_ptr_array_free(argbuf, TRUE); + argbuf = NULL; + + if (!exist) { + ok = imap_cmd_create(session, imap_path); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't create mailbox\n")); + g_free(imap_path); + g_free(dirpath); + return NULL; + } + + ok = imap_cmd_subscribe(session, imap_path); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't subscribe mailbox\n")); + } + + argbuf = g_ptr_array_new(); + ok = imap_cmd_list(session, NULL, imap_path, argbuf); + if (ok != IMAP_SUCCESS) { + log_warning("LIST failed\n"); + g_free(imap_path); + g_free(dirpath); + g_ptr_array_free(argbuf, TRUE); + return NULL; + } + + for (i = 0; i < argbuf->len; i++) { + gchar *str; + str = g_ptr_array_index(argbuf, i); + if (!strncmp(str, "LIST ", 5)) { + if (strcasestr(str + 5, "\\Noinferiors")) + no_sub = TRUE; + if (strcasestr(str + 5, "\\Noselect")) + no_select = TRUE; + break; + } + } + g_ptr_array_free(argbuf, TRUE); + } + + } + + new_item = folder_item_new(new_name, dirpath); + new_item->no_sub = no_sub; + new_item->no_select = no_select; + folder_item_append(parent, new_item); + g_free(imap_path); + g_free(dirpath); + + dirpath = folder_item_get_path(new_item); + if (!is_dir_exist(dirpath)) + make_dir_hier(dirpath); + g_free(dirpath); + + return new_item; +} + +static gint imap_rename_folder_real(Folder *folder, FolderItem *item, + FolderItem *new_parent, const gchar *name) +{ + gchar *newpath; + gchar *real_oldpath; + gchar *real_newpath; + gchar *paths[2]; + gchar *old_cache_dir; + gchar *new_cache_dir; + IMAPSession *session; + gchar separator; + gint ok; + gint exists, recent, unseen; + guint32 uid_validity; + gchar *old_id, *new_id; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(folder == item->folder, -1); + g_return_val_if_fail(item->path != NULL, -1); + g_return_val_if_fail(new_parent != NULL || name != NULL, -1); + if (new_parent) { + g_return_val_if_fail(item != new_parent, -1); + g_return_val_if_fail(item->parent != new_parent, -1); + g_return_val_if_fail(item->folder == new_parent->folder, -1); + if (g_node_is_ancestor(item->node, new_parent->node)) { + g_warning("folder to be moved is ancestor of new parent\n"); + return -1; + } + } + + session = imap_session_get(folder); + if (!session) return -1; + + real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path); + + g_free(session->mbox); + session->mbox = NULL; + ok = imap_cmd_examine(session, "INBOX", + &exists, &recent, &unseen, &uid_validity); + if (ok != IMAP_SUCCESS) { + g_free(real_oldpath); + return -1; + } + + separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path); + if (new_parent) { + if (name) { + if (new_parent->path) + newpath = g_strconcat(new_parent->path, + "/", name, NULL); + else + newpath = g_strdup(name); + } else { + gchar *name_; + + name_ = g_path_get_basename(item->path); + if (new_parent->path) + newpath = g_strconcat(new_parent->path, + "/", name_, NULL); + else + newpath = g_strdup(name_); + AUTORELEASE_STR(name_, ); + name = name_; + } + } else { + if (strchr(item->path, '/')) { + gchar *dirpath; + + dirpath = g_dirname(item->path); + newpath = g_strconcat(dirpath, "/", name, NULL); + g_free(dirpath); + } else + newpath = g_strdup(name); + } + + real_newpath = imap_utf8_to_modified_utf7(newpath); + imap_path_separator_subst(real_newpath, separator); + + ok = imap_cmd_rename(session, real_oldpath, real_newpath); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't rename mailbox: %s to %s\n"), + real_oldpath, real_newpath); + g_free(real_oldpath); + g_free(newpath); + g_free(real_newpath); + return -1; + } + + old_id = folder_item_get_identifier(item); + + if (new_parent) { + g_node_unlink(item->node); + g_node_append(new_parent->node, item->node); + item->parent = new_parent; + } + + g_free(item->name); + item->name = g_strdup(name); + + old_cache_dir = folder_item_get_path(item); + + paths[0] = g_strdup(item->path); + paths[1] = newpath; + g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + imap_rename_folder_func, paths); + + if (is_dir_exist(old_cache_dir)) { + new_cache_dir = folder_item_get_path(item); + if (g_rename(old_cache_dir, new_cache_dir) < 0) { + FILE_OP_ERROR(old_cache_dir, "rename"); + } + g_free(new_cache_dir); + } + + g_free(old_cache_dir); + g_free(paths[0]); + g_free(newpath); + g_free(real_oldpath); + g_free(real_newpath); + + new_id = folder_item_get_identifier(item); + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "move-folder", item, + old_id, new_id); + g_free(new_id); + g_free(old_id); + + return 0; +} + +static gint imap_rename_folder(Folder *folder, FolderItem *item, + const gchar *name) +{ + return imap_rename_folder_real(folder, item, NULL, name); +} + +static gint imap_move_folder(Folder *folder, FolderItem *item, + FolderItem *new_parent) +{ + return imap_rename_folder_real(folder, item, new_parent, NULL); +} + +static gint imap_remove_folder(Folder *folder, FolderItem *item) +{ + gint ok; + IMAPSession *session; + gchar *path; + gchar *cache_dir; + gint exists, recent, unseen; + guint32 uid_validity; + + 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); + + session = imap_session_get(folder); + if (!session) return -1; + + path = imap_get_real_path(IMAP_FOLDER(folder), item->path); + + ok = imap_cmd_examine(session, "INBOX", + &exists, &recent, &unseen, &uid_validity); + if (ok != IMAP_SUCCESS) { + g_free(path); + return -1; + } + + ok = imap_cmd_delete(session, path); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't delete mailbox\n")); + g_free(path); + return -1; + } + + g_free(path); + cache_dir = folder_item_get_path(item); + if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0) + g_warning("can't remove directory '%s'\n", cache_dir); + g_free(cache_dir); + + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "remove-folder", item); + folder_item_remove(item); + + return 0; +} + +typedef struct _IMAPGetData +{ + FolderItem *item; + gint exists; + gboolean update_count; + GSList *newlist; +} IMAPGetData; + +static gint imap_get_uncached_messages_progress_func(IMAPSession *session, + gint count, gint total, + gpointer data) +{ + status_print(_("Getting message headers (%d / %d)"), count, total); + progress_show(count, total); +#ifndef USE_THREADS + ui_update(); +#endif + return 0; +} + +static gint imap_get_uncached_messages_func(IMAPSession *session, gpointer data) +{ + gchar *tmp; + GSList *newlist = NULL; + GSList *llast = NULL; + GString *str; + MsgInfo *msginfo; + gint count = 1; + GTimeVal tv_prev, tv_cur; + IMAPGetData *get_data = (IMAPGetData *)data; + FolderItem *item = get_data->item; + gint exists = get_data->exists; + gboolean update_count = get_data->update_count; + + g_get_current_time(&tv_prev); +#ifndef USE_THREADS + ui_update(); +#endif + +#if USE_THREADS + ((IMAPRealSession *)session)->prog_total = exists; +#endif + + str = g_string_new(NULL); + + for (;;) { + if (exists > 0 && count <= exists) { + g_get_current_time(&tv_cur); + if (tv_cur.tv_sec > tv_prev.tv_sec || + tv_cur.tv_usec - tv_prev.tv_usec > + PROGRESS_UPDATE_INTERVAL * 1000) { +#if USE_THREADS + ((IMAPRealSession *)session)->prog_count = count; + g_main_context_wakeup(NULL); +#else + imap_get_uncached_messages_progress_func + (session, count, exists, data); +#endif + tv_prev = tv_cur; + } + } + ++count; + + if (sock_getline(SESSION(session)->sock, &tmp) < 0) { + log_warning(_("error occurred while getting envelope.\n")); + g_string_free(str, TRUE); + return IMAP_SOCKET; + } + strretchomp(tmp); + if (tmp[0] != '*' || tmp[1] != ' ') { + log_print("IMAP4< %s\n", tmp); + g_free(tmp); + break; + } + if (strstr(tmp, "FETCH") == NULL) { + log_print("IMAP4< %s\n", tmp); + g_free(tmp); + continue; + } + log_print("IMAP4< %s\n", tmp); + g_string_assign(str, tmp); + g_free(tmp); + + msginfo = imap_parse_envelope(session, item, str); + if (!msginfo) { + log_warning(_("can't parse envelope: %s\n"), str->str); + continue; + } + if (update_count) { + if (MSG_IS_NEW(msginfo->flags)) + item->new++; + if (MSG_IS_UNREAD(msginfo->flags)) + item->unread++; + } + if (item->stype == F_QUEUE) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED); + } else if (item->stype == F_DRAFT) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT); + } + + msginfo->folder = item; + + if (!newlist) + llast = newlist = g_slist_append(newlist, msginfo); + else { + llast = g_slist_append(llast, msginfo); + llast = llast->next; + } + + if (update_count) + item->total++; + } + + g_string_free(str, TRUE); + + session_set_access_time(SESSION(session)); + + get_data->newlist = newlist; + return IMAP_SUCCESS; +} + +static GSList *imap_get_uncached_messages(IMAPSession *session, + FolderItem *item, + guint32 first_uid, guint32 last_uid, + gint exists, gboolean update_count) +{ + IMAPGetData get_data = {item, exists, update_count, NULL}; + gchar seq_set[22]; + gint ok; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->folder != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_IMAP, NULL); + g_return_val_if_fail(first_uid <= last_uid, NULL); + + if (first_uid == 0 && last_uid == 0) + strcpy(seq_set, "1:*"); + else + g_snprintf(seq_set, sizeof(seq_set), "%u:%u", + first_uid, last_uid); + if (imap_cmd_envelope(session, seq_set) != IMAP_SUCCESS) { + log_warning(_("can't get envelope\n")); + return NULL; + } + +#if USE_THREADS + ok = imap_thread_run_progress(session, imap_get_uncached_messages_func, + imap_get_uncached_messages_progress_func, + &get_data); +#else + ok = imap_get_uncached_messages_func(session, &get_data); +#endif + + progress_show(0, 0); + return get_data.newlist; +} + +static void imap_delete_cached_message(FolderItem *item, guint32 uid) +{ + gchar *dir; + gchar *file; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(FOLDER_TYPE(item->folder) == F_IMAP); + + dir = folder_item_get_path(item); + file = g_strdup_printf("%s%c%u", dir, G_DIR_SEPARATOR, uid); + + debug_print("Deleting cached message: %s\n", file); + + g_unlink(file); + + g_free(file); + g_free(dir); +} + +static GSList *imap_delete_cached_messages(GSList *mlist, FolderItem *item, + guint32 first_uid, guint32 last_uid) +{ + GSList *cur, *next; + MsgInfo *msginfo; + gchar *dir; + + g_return_val_if_fail(item != NULL, mlist); + g_return_val_if_fail(item->folder != NULL, mlist); + g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_IMAP, mlist); + + if (first_uid == 0 && last_uid == 0) + return mlist; + + debug_print("Deleting cached messages %u - %u ... ", + first_uid, last_uid); + + dir = folder_item_get_path(item); + if (is_dir_exist(dir)) + remove_numbered_files(dir, first_uid, last_uid); + g_free(dir); + + for (cur = mlist; cur != NULL; ) { + next = cur->next; + + msginfo = (MsgInfo *)cur->data; + if (msginfo != NULL && first_uid <= msginfo->msgnum && + msginfo->msgnum <= last_uid) { + procmsg_msginfo_free(msginfo); + mlist = g_slist_remove(mlist, msginfo); + } + + cur = next; + } + + debug_print("done.\n"); + + return mlist; +} + +static void imap_delete_all_cached_messages(FolderItem *item) +{ + gchar *dir; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(FOLDER_TYPE(item->folder) == F_IMAP); + + debug_print("Deleting all cached messages... "); + + dir = folder_item_get_path(item); + if (is_dir_exist(dir)) + remove_all_numbered_files(dir); + g_free(dir); + + debug_print("done.\n"); +} + +#if USE_SSL +static SockInfo *imap_open(const gchar *server, gushort port, + SocksInfo *socks_info, SSLType ssl_type) +#else +static SockInfo *imap_open(const gchar *server, gushort port, + SocksInfo *socks_info) +#endif +{ + SockInfo *sock = NULL; + const gchar *server_; + gushort port_; +#if USE_THREADS + gint conn_id; +#endif + + if (socks_info) { + server_ = socks_info->proxy_host; + port_ = socks_info->proxy_port; + } else { + server_ = server; + port_ = port; + } + +#if USE_THREADS + if ((conn_id = sock_connect_async_thread(server_, port_)) < 0 || + sock_connect_async_thread_wait(conn_id, &sock) < 0) { + log_warning(_("Can't connect to IMAP4 server: %s:%d\n"), + server, port); + return NULL; + } +#else + if ((sock = sock_connect(server_, port_)) == NULL) { + log_warning(_("Can't connect to IMAP4 server: %s:%d\n"), + server, port); + return NULL; + } +#endif + + if (socks_info) { + if (socks_connect(sock, server, port, socks_info) < 0) { + log_warning("Can't establish SOCKS connection: %s:%d\n", + server, port); + sock_close(sock); + return NULL; + } + } + +#if USE_SSL + if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) { + log_warning(_("Can't establish IMAP4 session with: %s:%d\n"), + server, port); + sock_close(sock); + return NULL; + } +#endif + + return sock; +} + +static GList *imap_parse_namespace_str(gchar *str) +{ + gchar *p = str; + gchar *name; + gchar *separator; + IMAPNameSpace *namespace; + GList *ns_list = NULL; + + while (*p != '\0') { + /* parse ("#foo" "/") */ + + while (*p && *p != '(') p++; + if (*p == '\0') break; + p++; + + while (*p && *p != '"') p++; + if (*p == '\0') break; + p++; + name = p; + + while (*p && *p != '"') p++; + if (*p == '\0') break; + *p = '\0'; + p++; + + while (*p && g_ascii_isspace(*p)) p++; + if (*p == '\0') break; + if (strncmp(p, "NIL", 3) == 0) + separator = NULL; + else if (*p == '"') { + p++; + separator = p; + while (*p && *p != '"') p++; + if (*p == '\0') break; + *p = '\0'; + p++; + } else break; + + while (*p && *p != ')') p++; + if (*p == '\0') break; + p++; + + namespace = g_new(IMAPNameSpace, 1); + namespace->name = g_strdup(name); + namespace->separator = separator ? separator[0] : '\0'; + ns_list = g_list_append(ns_list, namespace); + } + + return ns_list; +} + +static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder) +{ + gchar *ns_str = NULL; + gchar **str_array; + + g_return_if_fail(session != NULL); + g_return_if_fail(folder != NULL); + + if (folder->ns_personal != NULL || + folder->ns_others != NULL || + folder->ns_shared != NULL) + return; + + if (imap_cmd_namespace(session, &ns_str) != IMAP_SUCCESS) { + log_warning(_("can't get namespace\n")); + imap_get_namespace_by_list(session, folder); + return; + } + + str_array = strsplit_parenthesis(ns_str, '(', ')', 3); + if (str_array[0]) + folder->ns_personal = imap_parse_namespace_str(str_array[0]); + if (str_array[0] && str_array[1]) + folder->ns_others = imap_parse_namespace_str(str_array[1]); + if (str_array[0] && str_array[1] && str_array[2]) + folder->ns_shared = imap_parse_namespace_str(str_array[2]); + g_strfreev(str_array); + g_free(ns_str); +} + +static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder) +{ + GSList *item_list, *cur; + gchar separator = '\0'; + IMAPNameSpace *namespace; + + g_return_if_fail(session != NULL); + g_return_if_fail(folder != NULL); + + if (folder->ns_personal != NULL || + folder->ns_others != NULL || + folder->ns_shared != NULL) + return; + + if (imap_cmd_gen_send(session, "LIST \"\" \"\"") != IMAP_SUCCESS) + return; + item_list = imap_parse_list(session, "", &separator); + for (cur = item_list; cur != NULL; cur = cur->next) + folder_item_destroy(FOLDER_ITEM(cur->data)); + g_slist_free(item_list); + + namespace = g_new(IMAPNameSpace, 1); + namespace->name = g_strdup(""); + namespace->separator = separator; + folder->ns_personal = g_list_append(NULL, namespace); +} + +static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list, + const gchar *path) +{ + IMAPNameSpace *namespace = NULL; + gchar *tmp_path, *name; + + if (!path) path = ""; + + for (; ns_list != NULL; ns_list = ns_list->next) { + IMAPNameSpace *tmp_ns = ns_list->data; + + Xstrcat_a(tmp_path, path, "/", return namespace); + Xstrdup_a(name, tmp_ns->name, return namespace); + if (tmp_ns->separator && tmp_ns->separator != '/') { + subst_char(tmp_path, tmp_ns->separator, '/'); + subst_char(name, tmp_ns->separator, '/'); + } + if (strncmp(tmp_path, name, strlen(name)) == 0) + namespace = tmp_ns; + } + + return namespace; +} + +static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder, + const gchar *path) +{ + IMAPNameSpace *namespace; + + g_return_val_if_fail(folder != NULL, NULL); + + namespace = imap_find_namespace_from_list(folder->ns_personal, path); + if (namespace) return namespace; + namespace = imap_find_namespace_from_list(folder->ns_others, path); + if (namespace) return namespace; + namespace = imap_find_namespace_from_list(folder->ns_shared, path); + if (namespace) return namespace; + + return NULL; +} + +static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path) +{ + IMAPNameSpace *namespace; + gchar separator = '/'; + + namespace = imap_find_namespace(folder, path); + if (namespace && namespace->separator) + separator = namespace->separator; + + return separator; +} + +static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path) +{ + gchar *real_path; + gchar separator; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(path != NULL, NULL); + + real_path = imap_utf8_to_modified_utf7(path); + separator = imap_get_path_separator(folder, path); + imap_path_separator_subst(real_path, separator); + + return real_path; +} + +static gchar *imap_parse_atom(IMAPSession *session, gchar *src, + gchar *dest, gint dest_len, GString *str) +{ + gchar *cur_pos = src; + gchar *nextline; + + g_return_val_if_fail(str != NULL, cur_pos); + + /* read the next line if the current response buffer is empty */ + while (g_ascii_isspace(*cur_pos)) cur_pos++; + while (*cur_pos == '\0') { + if (sock_getline(SESSION(session)->sock, &nextline) < 0) + return cur_pos; + g_string_assign(str, nextline); + cur_pos = str->str; + strretchomp(nextline); + /* log_print("IMAP4< %s\n", nextline); */ + debug_print("IMAP4< %s\n", nextline); + g_free(nextline); + + while (g_ascii_isspace(*cur_pos)) cur_pos++; + } + + if (*cur_pos == '~' && *(cur_pos + 1) == '{') + cur_pos++; + + if (!strncmp(cur_pos, "NIL", 3)) { + *dest = '\0'; + cur_pos += 3; + } else if (*cur_pos == '\"') { + gchar *p; + + p = get_quoted(cur_pos, '\"', dest, dest_len); + cur_pos = p ? p : cur_pos + 2; + } else if (*cur_pos == '{') { + gchar buf[32]; + gint len; + gint block_len = 0; + + cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf)); + len = atoi(buf); + g_return_val_if_fail(len >= 0, cur_pos); + + g_string_truncate(str, 0); + cur_pos = str->str; + + do { + gint cur_len; + + cur_len = sock_getline(SESSION(session)->sock, + &nextline); + if (cur_len < 0) + return cur_pos; + block_len += cur_len; + subst_null(nextline, cur_len, ' '); + g_string_append(str, nextline); + cur_pos = str->str; + strretchomp(nextline); + /* log_print("IMAP4< %s\n", nextline); */ + debug_print("IMAP4< %s\n", nextline); + g_free(nextline); + } while (block_len < len); + + memcpy(dest, cur_pos, MIN(len, dest_len - 1)); + dest[MIN(len, dest_len - 1)] = '\0'; + cur_pos += len; + } + + return cur_pos; +} + +static gchar *imap_get_header(IMAPSession *session, gchar *cur_pos, + gchar **headers, GString *str) +{ + gchar *nextline; + gchar buf[IMAPBUFSIZE]; + gint len; + gint block_len = 0; + + *headers = NULL; + + g_return_val_if_fail(str != NULL, cur_pos); + + while (g_ascii_isspace(*cur_pos)) cur_pos++; + if (*cur_pos == '~' && *(cur_pos + 1) == '{') + cur_pos++; + + if (*cur_pos == '"') { + cur_pos = strchr_cpy(cur_pos + 1, '"', buf, sizeof(buf)); + if (!cur_pos) + return NULL; + len = strlen(buf); + *headers = g_strdup(buf); + while (g_ascii_isspace(*cur_pos)) cur_pos++; + return cur_pos; + } + + g_return_val_if_fail(*cur_pos == '{', cur_pos); + + cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf)); + len = atoi(buf); + g_return_val_if_fail(len >= 0, cur_pos); + + g_string_truncate(str, 0); + cur_pos = str->str; + + do { + gint cur_len; + + cur_len = sock_getline(SESSION(session)->sock, &nextline); + if (cur_len < 0) + return cur_pos; + block_len += cur_len; + subst_null(nextline, cur_len, ' '); + g_string_append(str, nextline); + cur_pos = str->str; + /* strretchomp(nextline); */ + /* debug_print("IMAP4< %s\n", nextline); */ + g_free(nextline); + } while (block_len < len); + + debug_print("IMAP4< [contents of RFC822.HEADER]\n"); + + *headers = g_strndup(cur_pos, len); + cur_pos += len; + + while (g_ascii_isspace(*cur_pos)) cur_pos++; + while (*cur_pos == '\0') { + if (sock_getline(SESSION(session)->sock, &nextline) < 0) + return cur_pos; + g_string_assign(str, nextline); + cur_pos = str->str; + strretchomp(nextline); + debug_print("IMAP4< %s\n", nextline); + g_free(nextline); + + while (g_ascii_isspace(*cur_pos)) cur_pos++; + } + + return cur_pos; +} + +static MsgFlags imap_parse_flags(const gchar *flag_str) +{ + const gchar *p = flag_str; + MsgFlags flags = {0, 0}; + + flags.perm_flags = MSG_UNREAD; + + while (*p != '\0') { + if (g_ascii_strncasecmp(p, "\\Recent", 7) == 0 && + MSG_IS_UNREAD(flags)) { + MSG_SET_PERM_FLAGS(flags, MSG_NEW); + } else if (g_ascii_strncasecmp(p, "\\Seen", 5) == 0) { + MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD); + } else if (g_ascii_strncasecmp(p, "\\Deleted", 8) == 0) { + MSG_SET_PERM_FLAGS(flags, MSG_DELETED); + } else if (g_ascii_strncasecmp(p, "\\Flagged", 8) == 0) { + MSG_SET_PERM_FLAGS(flags, MSG_MARKED); + } else if (g_ascii_strncasecmp(p, "\\Answered", 9) == 0) { + MSG_SET_PERM_FLAGS(flags, MSG_REPLIED); + } else if (g_ascii_strncasecmp(p, "$label", 6) == 0) { + /* color labels */ + if (*(p + 6) >= '1' && *(p + 6) <= '7') { + guint color = *(p + 6) - '1' + 1; + MSG_UNSET_PERM_FLAGS(flags, + MSG_CLABEL_FLAG_MASK); + MSG_SET_COLORLABEL_VALUE(flags, color); + } + } + + while (*p && !g_ascii_isspace(*p)) p++; + while (g_ascii_isspace(*p)) p++; + } + + return flags; +} + +static IMAPFlags imap_parse_imap_flags(const gchar *flag_str) +{ + const gchar *p = flag_str; + IMAPFlags flags = 0; + + while (*p != '\0') { + if (g_ascii_strncasecmp(p, "\\Seen", 5) == 0) { + flags |= IMAP_FLAG_SEEN; + } else if (g_ascii_strncasecmp(p, "\\Deleted", 8) == 0) { + flags |= IMAP_FLAG_DELETED; + } else if (g_ascii_strncasecmp(p, "\\Flagged", 8) == 0) { + flags |= IMAP_FLAG_FLAGGED; + } else if (g_ascii_strncasecmp(p, "\\Answered", 9) == 0) { + flags |= IMAP_FLAG_ANSWERED; + } else if (g_ascii_strncasecmp(p, "$label", 6) == 0) { + /* color labels */ + if (*(p + 6) >= '1' && *(p + 6) <= '7') { + guint color = *(p + 6) - '1' + 1; + MSG_UNSET_FLAGS(flags, MSG_CLABEL_FLAG_MASK); + IMAP_SET_COLORLABEL_VALUE(flags, color); + } + } + + while (*p && !g_ascii_isspace(*p)) p++; + while (g_ascii_isspace(*p)) p++; + } + + return flags; +} + +static MsgInfo *imap_parse_envelope(IMAPSession *session, FolderItem *item, + GString *line_str) +{ + gchar buf[IMAPBUFSIZE]; + MsgInfo *msginfo = NULL; + gchar *cur_pos; + gint msgnum; + guint32 uid = 0; + size_t size = 0; + MsgFlags flags = {0, 0}, imap_flags = {0, 0}; + + g_return_val_if_fail(line_str != NULL, NULL); + g_return_val_if_fail(line_str->str[0] == '*' && + line_str->str[1] == ' ', NULL); + + MSG_SET_TMP_FLAGS(flags, MSG_IMAP); + 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); + } + + cur_pos = line_str->str + 2; + +#define PARSE_ONE_ELEMENT(ch) \ +{ \ + cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \ + if (cur_pos == NULL) { \ + g_warning("cur_pos == NULL\n"); \ + procmsg_msginfo_free(msginfo); \ + return NULL; \ + } \ +} + + PARSE_ONE_ELEMENT(' '); + msgnum = atoi(buf); + + PARSE_ONE_ELEMENT(' '); + g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL); + + g_return_val_if_fail(*cur_pos == '(', NULL); + cur_pos++; + + while (*cur_pos != '\0' && *cur_pos != ')') { + while (*cur_pos == ' ') cur_pos++; + + if (!strncmp(cur_pos, "UID ", 4)) { + cur_pos += 4; + uid = strtoul(cur_pos, &cur_pos, 10); + } else if (!strncmp(cur_pos, "FLAGS ", 6)) { + cur_pos += 6; + if (*cur_pos != '(') { + g_warning("FLAGS: *cur_pos != '('\n"); + procmsg_msginfo_free(msginfo); + return NULL; + } + cur_pos++; + PARSE_ONE_ELEMENT(')'); + imap_flags = imap_parse_flags(buf); + } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) { + cur_pos += 12; + size = strtol(cur_pos, &cur_pos, 10); + } else if (!strncmp(cur_pos, "RFC822.HEADER", 13)) { + gchar *headers; + + cur_pos += 13; + cur_pos = imap_get_header(session, cur_pos, &headers, + line_str); + if (cur_pos == NULL) { + g_warning("RFC822.HEADER: cur_pos == NULL\n"); + procmsg_msginfo_free(msginfo); + return NULL; + } + if (!msginfo) + msginfo = procheader_parse_str(headers, flags, FALSE); + g_free(headers); + } else { + g_warning("invalid FETCH response: %s\n", cur_pos); + break; + } + } + +#undef PARSE_ONE_ELEMENT + + if (msginfo) { + msginfo->msgnum = uid; + msginfo->size = size; + msginfo->flags.tmp_flags |= imap_flags.tmp_flags; + msginfo->flags.perm_flags = imap_flags.perm_flags; + } + + return msginfo; +} + +static gint imap_msg_list_change_perm_flags(GSList *msglist, MsgPermFlags flags, + gboolean is_set) +{ + Folder *folder; + IMAPSession *session; + IMAPFlags iflags = 0; + MsgInfo *msginfo; + GSList *seq_list, *cur; + gint ok = IMAP_SUCCESS; + + if (msglist == NULL) return IMAP_SUCCESS; + + msginfo = (MsgInfo *)msglist->data; + g_return_val_if_fail(msginfo != NULL, -1); + + g_return_val_if_fail(MSG_IS_IMAP(msginfo->flags), -1); + g_return_val_if_fail(msginfo->folder != NULL, -1); + g_return_val_if_fail(msginfo->folder->folder != NULL, -1); + + folder = msginfo->folder->folder; + g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path, + NULL, NULL, NULL, NULL); + if (ok != IMAP_SUCCESS) + return ok; + + seq_list = imap_get_seq_set_from_msglist(msglist, 0); + + if (flags & MSG_MARKED) iflags |= IMAP_FLAG_FLAGGED; + if (flags & MSG_REPLIED) iflags |= IMAP_FLAG_ANSWERED; + + for (cur = seq_list; cur != NULL; cur = cur->next) { + gchar *seq_set = (gchar *)cur->data; + + if (iflags) { + ok = imap_set_message_flags(session, seq_set, iflags, + is_set); + if (ok != IMAP_SUCCESS) break; + } + + if (flags & MSG_UNREAD) { + ok = imap_set_message_flags(session, seq_set, + IMAP_FLAG_SEEN, !is_set); + if (ok != IMAP_SUCCESS) break; + } + } + + imap_seq_set_free(seq_list); + + return ok; +} + +gint imap_msg_set_perm_flags(MsgInfo *msginfo, MsgPermFlags flags) +{ + GSList msglist; + + msglist.data = msginfo; + msglist.next = NULL; + + return imap_msg_list_change_perm_flags(&msglist, flags, TRUE); +} + +gint imap_msg_unset_perm_flags(MsgInfo *msginfo, MsgPermFlags flags) +{ + GSList msglist; + + msglist.data = msginfo; + msglist.next = NULL; + + return imap_msg_list_change_perm_flags(&msglist, flags, FALSE); +} + +gint imap_msg_list_set_perm_flags(GSList *msglist, MsgPermFlags flags) +{ + return imap_msg_list_change_perm_flags(msglist, flags, TRUE); +} + +gint imap_msg_list_unset_perm_flags(GSList *msglist, MsgPermFlags flags) +{ + return imap_msg_list_change_perm_flags(msglist, flags, FALSE); +} + +gint imap_msg_list_set_colorlabel_flags(GSList *msglist, guint color) +{ + Folder *folder; + IMAPSession *session; + IMAPFlags iflags = 0; + MsgInfo *msginfo; + GSList *seq_list, *cur; + gint ok = IMAP_SUCCESS; + + if (msglist == NULL) return IMAP_SUCCESS; + + msginfo = (MsgInfo *)msglist->data; + g_return_val_if_fail(msginfo != NULL, -1); + + g_return_val_if_fail(MSG_IS_IMAP(msginfo->flags), -1); + g_return_val_if_fail(msginfo->folder != NULL, -1); + g_return_val_if_fail(msginfo->folder->folder != NULL, -1); + + folder = msginfo->folder->folder; + g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path, + NULL, NULL, NULL, NULL); + if (ok != IMAP_SUCCESS) + return ok; + + seq_list = imap_get_seq_set_from_msglist(msglist, 0); + + IMAP_SET_COLORLABEL_VALUE(iflags, color); + + for (cur = seq_list; cur != NULL; cur = cur->next) { + gchar *seq_set = (gchar *)cur->data; + + ok = imap_cmd_store(session, seq_set, + "-FLAGS.SILENT ($label1 $label2 $label3 $label4 $label5 $label6 $label7)"); + if (ok != IMAP_SUCCESS) break; + + if (iflags) { + ok = imap_set_message_flags(session, seq_set, iflags, + TRUE); + if (ok != IMAP_SUCCESS) break; + } + } + + imap_seq_set_free(seq_list); + + return ok; +} + +static gchar *imap_get_flag_str(IMAPFlags flags) +{ + GString *str; + gchar *ret; + guint color; + + str = g_string_new(NULL); + + if (IMAP_IS_SEEN(flags)) g_string_append(str, "\\Seen "); + if (IMAP_IS_ANSWERED(flags)) g_string_append(str, "\\Answered "); + if (IMAP_IS_FLAGGED(flags)) g_string_append(str, "\\Flagged "); + if (IMAP_IS_DELETED(flags)) g_string_append(str, "\\Deleted "); + if (IMAP_IS_DRAFT(flags)) g_string_append(str, "\\Draft "); + + if ((color = IMAP_GET_COLORLABEL_VALUE(flags)) != 0) { + g_string_append_printf(str, "$label%u", color); + } + + if (str->len > 0 && str->str[str->len - 1] == ' ') + g_string_truncate(str, str->len - 1); + + ret = str->str; + g_string_free(str, FALSE); + + return ret; +} + +static gint imap_set_message_flags(IMAPSession *session, + const gchar *seq_set, + IMAPFlags flags, + gboolean is_set) +{ + gchar *cmd; + gchar *flag_str; + gint ok; + + flag_str = imap_get_flag_str(flags); + cmd = g_strconcat(is_set ? "+FLAGS.SILENT (" : "-FLAGS.SILENT (", + flag_str, ")", NULL); + g_free(flag_str); + + ok = imap_cmd_store(session, seq_set, cmd); + g_free(cmd); + + return ok; +} + +static gint imap_select(IMAPSession *session, IMAPFolder *folder, + const gchar *path, + gint *exists, gint *recent, gint *unseen, + guint32 *uid_validity) +{ + gchar *real_path; + gint ok; + gint exists_, recent_, unseen_; + guint32 uid_validity_; + + if (!exists || !recent || !unseen || !uid_validity) { + if (session->mbox && strcmp(session->mbox, path) == 0) + return IMAP_SUCCESS; + exists = &exists_; + recent = &recent_; + unseen = &unseen_; + uid_validity = &uid_validity_; + } + + g_free(session->mbox); + session->mbox = NULL; + + real_path = imap_get_real_path(folder, path); + ok = imap_cmd_select(session, real_path, + exists, recent, unseen, uid_validity); + if (ok != IMAP_SUCCESS) + log_warning(_("can't select folder: %s\n"), real_path); + else + session->mbox = g_strdup(path); + g_free(real_path); + + return ok; +} + +#define THROW(err) { ok = err; goto catch; } + +static gint imap_status(IMAPSession *session, IMAPFolder *folder, + const gchar *path, + gint *messages, gint *recent, + guint32 *uid_next, guint32 *uid_validity, + gint *unseen) +{ + gchar *real_path; + gchar *real_path_; + gint ok; + GPtrArray *argbuf = NULL; + gchar *str; + + if (messages && recent && uid_next && uid_validity && unseen) { + *messages = *recent = *uid_next = *uid_validity = *unseen = 0; + argbuf = g_ptr_array_new(); + } + + real_path = imap_get_real_path(folder, path); + QUOTE_IF_REQUIRED(real_path_, real_path); + ok = imap_cmd_gen_send(session, "STATUS %s " + "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)", + real_path_); + if (ok != IMAP_SUCCESS) { + log_warning("error on sending imap command: STATUS\n"); + THROW(ok); + } + ok = imap_cmd_ok(session, argbuf); + if (ok != IMAP_SUCCESS) + log_warning(_("error on imap command: STATUS\n")); + if (ok != IMAP_SUCCESS || !argbuf) THROW(ok); + + str = search_array_str(argbuf, "STATUS"); + if (!str) THROW(IMAP_ERROR); + + str = strrchr_with_skip_quote(str, '"', '('); + if (!str) THROW(IMAP_ERROR); + str++; + while (*str != '\0' && *str != ')') { + while (*str == ' ') str++; + + if (!strncmp(str, "MESSAGES ", 9)) { + str += 9; + *messages = strtol(str, &str, 10); + } else if (!strncmp(str, "RECENT ", 7)) { + str += 7; + *recent = strtol(str, &str, 10); + } else if (!strncmp(str, "UIDNEXT ", 8)) { + str += 8; + *uid_next = strtoul(str, &str, 10); + } else if (!strncmp(str, "UIDVALIDITY ", 12)) { + str += 12; + *uid_validity = strtoul(str, &str, 10); + } else if (!strncmp(str, "UNSEEN ", 7)) { + str += 7; + *unseen = strtol(str, &str, 10); + } else { + g_warning("invalid STATUS response: %s\n", str); + break; + } + } + +catch: + g_free(real_path); + if (argbuf) { + ptr_array_free_strings(argbuf); + g_ptr_array_free(argbuf, TRUE); + } + + return ok; +} + +#undef THROW + +static gboolean imap_has_capability(IMAPSession *session, + const gchar *capability) +{ + gchar **p; + + for (p = session->capability; *p != NULL; ++p) { + if (!g_ascii_strcasecmp(*p, capability)) + return TRUE; + } + + return FALSE; +} + +static void imap_capability_free(IMAPSession *session) +{ + if (session->capability) { + g_strfreev(session->capability); + session->capability = NULL; + } +} + + +/* low-level IMAP4rev1 commands */ + +#define THROW(err) { ok = err; goto catch; } + +static gint imap_cmd_capability(IMAPSession *session) +{ + gint ok; + GPtrArray *argbuf; + gchar *capability; + + argbuf = g_ptr_array_new(); + + if ((ok = imap_cmd_gen_send(session, "CAPABILITY")) != IMAP_SUCCESS) + THROW(ok); + if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok); + + capability = search_array_str(argbuf, "CAPABILITY "); + if (!capability) THROW(IMAP_ERROR); + + capability += strlen("CAPABILITY "); + + imap_capability_free(session); + session->capability = g_strsplit(capability, " ", -1); + +catch: + ptr_array_free_strings(argbuf); + g_ptr_array_free(argbuf, TRUE); + + return ok; +} + +#undef THROW + +static gint imap_cmd_auth_plain(IMAPSession *session, const gchar *user, + const gchar *pass) +{ + gchar *p; + gchar *response; + gchar *response64; + gint ok; + + p = response = g_malloc(strlen(user) * 2 + 2 + strlen(pass) + 1); + strcpy(p, user); + p += strlen(user) + 1; + strcpy(p, user); + p += strlen(user) + 1; + strcpy(p, pass); + p += strlen(pass); + + response64 = g_malloc((p - response) * 2 + 1); + base64_encode(response64, (guchar *)response, p - response); + g_free(response); + + log_print("IMAP4> ****************\n"); + sock_puts(SESSION(session)->sock, response64); + ok = imap_cmd_ok(session, NULL); + if (ok != IMAP_SUCCESS) + log_warning(_("IMAP4 authentication failed.\n")); + g_free(response64); + + return ok; +} + +static gint imap_cmd_auth_cram_md5(IMAPSession *session, const gchar *user, + const gchar *pass, const gchar *challenge64) +{ + gchar *challenge; + gint challenge_len; + gchar hexdigest[33]; + gchar *response; + gchar *response64; + gint ok; + + challenge = g_malloc(strlen(challenge64 + 2) + 1); + challenge_len = base64_decode((guchar *)challenge, challenge64 + 2, -1); + challenge[challenge_len] = '\0'; + log_print("IMAP< [Decoded: %s]\n", challenge); + + md5_hex_hmac(hexdigest, (guchar *)challenge, challenge_len, + (guchar *)pass, strlen(pass)); + g_free(challenge); + + response = g_strdup_printf("%s %s", user, hexdigest); + log_print("IMAP> [Encoded: %s]\n", response); + response64 = g_malloc((strlen(response) + 3) * 2 + 1); + base64_encode(response64, (guchar *)response, strlen(response)); + g_free(response); + + log_print("IMAP> %s\n", response64); + sock_puts(SESSION(session)->sock, response64); + ok = imap_cmd_ok(session, NULL); + if (ok != IMAP_SUCCESS) + log_warning(_("IMAP4 authentication failed.\n")); + + return ok; +} + +static gint imap_cmd_authenticate(IMAPSession *session, const gchar *user, + const gchar *pass, IMAPAuthType type) +{ + gchar *auth_type; + gint ok; + gchar *buf = NULL; + + g_return_val_if_fail((type == 0 || type == IMAP_AUTH_CRAM_MD5 || + type == IMAP_AUTH_PLAIN), IMAP_ERROR); + + if (type == IMAP_AUTH_PLAIN) + auth_type = "PLAIN"; + else + auth_type = "CRAM-MD5"; + + ok = imap_cmd_gen_send(session, "AUTHENTICATE %s", auth_type); + if (ok != IMAP_SUCCESS) { + g_free(buf); + return ok; + } + ok = imap_cmd_gen_recv(session, &buf); + if (ok != IMAP_SUCCESS || buf[0] != '+') { + g_free(buf); + return IMAP_ERROR; + } + + if (type == IMAP_AUTH_PLAIN) + ok = imap_cmd_auth_plain(session, user, pass); + else + ok = imap_cmd_auth_cram_md5(session, user, pass, buf); + + g_free(buf); + + return ok; +} + +static gint imap_cmd_login(IMAPSession *session, + const gchar *user, const gchar *pass) +{ + gchar *user_, *pass_; + gint ok; + + QUOTE_IF_REQUIRED(user_, user); + QUOTE_IF_REQUIRED(pass_, pass); + ok = imap_cmd_gen_send(session, "LOGIN %s %s", user_, pass_); + if (ok == IMAP_SUCCESS) + ok = imap_cmd_ok(session, NULL); + if (ok != IMAP_SUCCESS) + log_warning(_("IMAP4 login failed.\n")); + + return ok; +} + +static gint imap_cmd_logout(IMAPSession *session) +{ + if (imap_cmd_gen_send(session, "LOGOUT") != IMAP_SUCCESS) + return IMAP_ERROR; + return imap_cmd_ok(session, NULL); +} + +static gint imap_cmd_noop(IMAPSession *session) +{ + gint ret; + + ret = imap_cmd_gen_send(session, "NOOP"); + if (ret != IMAP_SUCCESS) + return ret; + return imap_cmd_ok(session, NULL); +} + +#if USE_SSL +static gint imap_cmd_starttls(IMAPSession *session) +{ + if (imap_cmd_gen_send(session, "STARTTLS") != IMAP_SUCCESS) + return IMAP_ERROR; + return imap_cmd_ok(session, NULL); +} +#endif + +#define THROW(err) { ok = err; goto catch; } + +static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str) +{ + gint ok; + GPtrArray *argbuf; + gchar *str; + + argbuf = g_ptr_array_new(); + + if ((ok = imap_cmd_gen_send(session, "NAMESPACE")) != IMAP_SUCCESS) + THROW(ok); + if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok); + + str = search_array_str(argbuf, "NAMESPACE"); + if (!str) THROW(IMAP_ERROR); + + *ns_str = g_strdup(str); + +catch: + ptr_array_free_strings(argbuf); + g_ptr_array_free(argbuf, TRUE); + + return ok; +} + +#undef THROW + +static gint imap_cmd_list(IMAPSession *session, const gchar *ref, + const gchar *mailbox, GPtrArray *argbuf) +{ + gchar *ref_, *mailbox_; + + if (!ref) ref = ""; + if (!mailbox) mailbox = ""; + + QUOTE_IF_REQUIRED(ref_, ref); + QUOTE_IF_REQUIRED(mailbox_, mailbox); + if (imap_cmd_gen_send(session, "LIST %s %s", ref_, mailbox_) != IMAP_SUCCESS) + return IMAP_ERROR; + + return imap_cmd_ok(session, argbuf); +} + +#define THROW goto catch + +static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder, + gboolean examine, + gint *exists, gint *recent, gint *unseen, + guint32 *uid_validity) +{ + gint ok; + gchar *resp_str; + GPtrArray *argbuf; + gchar *select_cmd; + gchar *folder_; + guint uid_validity_; + + *exists = *recent = *unseen = *uid_validity = 0; + argbuf = g_ptr_array_new(); + + if (examine) + select_cmd = "EXAMINE"; + else + select_cmd = "SELECT"; + + QUOTE_IF_REQUIRED(folder_, folder); + if ((ok = imap_cmd_gen_send(session, "%s %s", select_cmd, folder_)) != IMAP_SUCCESS) + THROW; + + if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW; + + resp_str = search_array_contain_str(argbuf, "EXISTS"); + if (resp_str) { + if (sscanf(resp_str,"%d EXISTS", exists) != 1) { + g_warning("imap_cmd_select(): invalid EXISTS line.\n"); + THROW; + } + } + + resp_str = search_array_contain_str(argbuf, "RECENT"); + if (resp_str) { + if (sscanf(resp_str, "%d RECENT", recent) != 1) { + g_warning("imap_cmd_select(): invalid RECENT line.\n"); + THROW; + } + } + + resp_str = search_array_contain_str(argbuf, "UIDVALIDITY"); + if (resp_str) { + if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", &uid_validity_) + != 1) { + g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n"); + THROW; + } + *uid_validity = uid_validity_; + } + + resp_str = search_array_contain_str(argbuf, "UNSEEN"); + if (resp_str) { + if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) { + g_warning("imap_cmd_select(): invalid UNSEEN line.\n"); + THROW; + } + } + +catch: + ptr_array_free_strings(argbuf); + g_ptr_array_free(argbuf, TRUE); + + return ok; +} + +static gint imap_cmd_select(IMAPSession *session, const gchar *folder, + gint *exists, gint *recent, gint *unseen, + guint32 *uid_validity) +{ + return imap_cmd_do_select(session, folder, FALSE, + exists, recent, unseen, uid_validity); +} + +static gint imap_cmd_examine(IMAPSession *session, const gchar *folder, + gint *exists, gint *recent, gint *unseen, + guint32 *uid_validity) +{ + return imap_cmd_do_select(session, folder, TRUE, + exists, recent, unseen, uid_validity); +} + +#undef THROW + +static gint imap_cmd_create(IMAPSession *session, const gchar *folder) +{ + gchar *folder_; + + QUOTE_IF_REQUIRED(folder_, folder); + if (imap_cmd_gen_send(session, "CREATE %s", folder_) != IMAP_SUCCESS) + return IMAP_ERROR; + + return imap_cmd_ok(session, NULL); +} + +static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder, + const gchar *new_folder) +{ + gchar *old_folder_, *new_folder_; + + QUOTE_IF_REQUIRED(old_folder_, old_folder); + QUOTE_IF_REQUIRED(new_folder_, new_folder); + if (imap_cmd_gen_send(session, "RENAME %s %s", old_folder_, new_folder_) != IMAP_SUCCESS) + return IMAP_ERROR; + + return imap_cmd_ok(session, NULL); +} + +static gint imap_cmd_delete(IMAPSession *session, const gchar *folder) +{ + gchar *folder_; + + QUOTE_IF_REQUIRED(folder_, folder); + if (imap_cmd_gen_send(session, "DELETE %s", folder_) != IMAP_SUCCESS) + return IMAP_ERROR; + + return imap_cmd_ok(session, NULL); +} + +static gint imap_cmd_subscribe(IMAPSession *session, const gchar *folder) +{ + gchar *folder_; + + QUOTE_IF_REQUIRED(folder_, folder); + if (imap_cmd_gen_send(session, "SUBSCRIBE %s", folder_) != IMAP_SUCCESS) + return IMAP_ERROR; + + return imap_cmd_ok(session, NULL); +} + +#define THROW(err) { ok = err; goto catch; } + +static gint imap_cmd_search(IMAPSession *session, const gchar *criteria, + GArray **result) +{ + gint ok; + GPtrArray *argbuf; + GArray *array; + gchar *str; + gchar *p, *ep; + gint i; + guint32 uid; + + g_return_val_if_fail(criteria != NULL, IMAP_ERROR); + g_return_val_if_fail(result != NULL, IMAP_ERROR); + + argbuf = g_ptr_array_new(); + + if ((ok = imap_cmd_gen_send(session, "UID SEARCH %s", criteria)) != IMAP_SUCCESS) + THROW(ok); + if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok); + + array = g_array_new(FALSE, FALSE, sizeof(guint32)); + + for (i = 0; i < argbuf->len; i++) { + str = g_ptr_array_index(argbuf, i); + if (strncmp(str, "SEARCH", 6) != 0) + continue; + + p = str + 6; + while (*p != '\0') { + uid = strtoul(p, &ep, 10); + if (p < ep && uid > 0) { + g_array_append_val(array, uid); + p = ep; + } else + break; + } + } + + *result = array; + +catch: + ptr_array_free_strings(argbuf); + g_ptr_array_free(argbuf, TRUE); + + return ok; +} + +typedef struct _IMAPCmdFetchData +{ + guint32 uid; + const gchar *filename; +} IMAPCmdFetchData; + +#define THROW(err) { ok = err; goto catch; } + +static gint imap_cmd_fetch_func(IMAPSession *session, gpointer data) +{ + const gchar *filename = ((IMAPCmdFetchData *)data)->filename; + gint ok; + gchar *buf; + gchar *cur_pos; + gchar size_str[32]; + glong size_num; + gint ret; + + while ((ok = imap_cmd_gen_recv(session, &buf)) == IMAP_SUCCESS) { + if (buf[0] != '*' || buf[1] != ' ') { + g_free(buf); + return IMAP_ERROR; + } + if (strstr(buf, "FETCH") != NULL && strstr(buf, "BODY") != NULL) + break; + g_free(buf); + } + if (ok != IMAP_SUCCESS) + THROW(ok); + +#define RETURN_ERROR_IF_FAIL(cond) \ + if (!(cond)) { \ + g_free(buf); \ + ok = imap_cmd_ok_real(session, NULL); \ + THROW(IMAP_ERROR); \ + } + + cur_pos = strchr(buf, '{'); + RETURN_ERROR_IF_FAIL(cur_pos != NULL); + cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str)); + RETURN_ERROR_IF_FAIL(cur_pos != NULL); + size_num = atol(size_str); + RETURN_ERROR_IF_FAIL(size_num >= 0); + + RETURN_ERROR_IF_FAIL(*cur_pos == '\0'); + +#undef RETURN_ERROR_IF_FAIL + + g_free(buf); + + if ((ret = recv_bytes_write_to_file(SESSION(session)->sock, + size_num, filename)) != 0) { + if (ret == -2) + THROW(IMAP_SOCKET); + } + + if (imap_cmd_gen_recv(session, &buf) != IMAP_SUCCESS) + THROW(IMAP_ERROR); + + if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') { + g_free(buf); + THROW(IMAP_ERROR); + } + g_free(buf); + + ok = imap_cmd_ok_real(session, NULL); + + if (ret != 0) + THROW(IMAP_ERROR); + +catch: + return ok; +} + +#undef THROW + +static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, + const gchar *filename) +{ + gint ok; + IMAPCmdFetchData fetch_data = {uid, filename}; + + g_return_val_if_fail(filename != NULL, IMAP_ERROR); + + ok = imap_cmd_gen_send(session, "UID FETCH %u BODY.PEEK[]", uid); + if (ok != IMAP_SUCCESS) + return ok; + +#if USE_THREADS + ok = imap_thread_run(session, imap_cmd_fetch_func, &fetch_data); +#else + ok = imap_cmd_fetch_func(session, &fetch_data); +#endif + + return ok; +} + +static void imap_get_date_time(gchar *buf, size_t len, stime_t timer) +{ + static gchar monthstr[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; + struct tm *lt; + gchar date_time[64]; + gchar tz[6]; + time_t timer_ = timer; + + lt = localtime(&timer_); + if (lt && lt->tm_mon >= 0 && lt->tm_mon < 12) { + strftime(date_time, sizeof(date_time), "%d-___-%Y %H:%M:%S", + lt); + tzoffset_buf(tz, &timer); + memcpy(date_time + 3, monthstr + lt->tm_mon * 3, 3); + g_snprintf(buf, len, "%s %s", date_time, tz); + } +} + +static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder, + const gchar *file, IMAPFlags flags, + guint32 *new_uid) +{ + gint ok; + MsgInfo *msginfo; + MsgFlags flags_ = {0, 0}; + gchar date_time[64] = ""; + gint size; + gchar *destfolder_; + gchar *flag_str; + guint new_uid_; + gchar *ret = NULL; + gchar buf[BUFFSIZE]; + FILE *fp; + FILE *tmp; + size_t read_len; + GPtrArray *argbuf; + gchar *resp_str; + + g_return_val_if_fail(file != NULL, IMAP_ERROR); + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + /* use Date: header as received date */ + msginfo = procheader_parse_stream(fp, flags_, FALSE); + imap_get_date_time(date_time, sizeof(date_time), msginfo->date_t); + procmsg_msginfo_free(msginfo); + + rewind(fp); + tmp = canonicalize_file_stream(fp, &size); + fclose(fp); + if (!tmp) + return -1; + + QUOTE_IF_REQUIRED(destfolder_, destfolder); + flag_str = imap_get_flag_str(flags); + if (date_time[0]) + ok = imap_cmd_gen_send(session, "APPEND %s (%s) \"%s\" {%d}", + destfolder_, flag_str, date_time, size); + else + ok = imap_cmd_gen_send(session, "APPEND %s (%s) {%d}", + destfolder_, flag_str, size); + g_free(flag_str); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't append %s to %s\n"), file, destfolder_); + fclose(tmp); + return ok; + } + + ok = imap_cmd_gen_recv(session, &ret); + if (ok != IMAP_SUCCESS || ret[0] != '+') { + log_warning(_("can't append %s to %s\n"), file, destfolder_); + g_free(ret); + fclose(tmp); + return IMAP_ERROR; + } + g_free(ret); + + log_print("IMAP4> %s\n", _("(sending file...)")); + + while ((read_len = fread(buf, 1, sizeof(buf), tmp)) > 0) { + if (read_len < sizeof(buf) && ferror(tmp)) + break; + if (sock_write_all(SESSION(session)->sock, buf, read_len) < 0) { + fclose(tmp); + return -1; + } + } + + if (ferror(tmp)) { + FILE_OP_ERROR(file, "fread"); + fclose(tmp); + return -1; + } + + sock_puts(SESSION(session)->sock, ""); + + fclose(tmp); + + if (new_uid != NULL) + *new_uid = 0; + + if (new_uid != NULL && session->uidplus) { + argbuf = g_ptr_array_new(); + + ok = imap_cmd_ok(session, argbuf); + if (ok != IMAP_SUCCESS) + log_warning(_("can't append message to %s\n"), + destfolder_); + else if (argbuf->len > 0) { + resp_str = g_ptr_array_index(argbuf, argbuf->len - 1); + if (resp_str && + sscanf(resp_str, "%*u OK [APPENDUID %*u %u]", + &new_uid_) == 1) { + *new_uid = new_uid_; + } + } + + ptr_array_free_strings(argbuf); + g_ptr_array_free(argbuf, TRUE); + } else + ok = imap_cmd_ok(session, NULL); + + return ok; +} + +static gint imap_cmd_copy(IMAPSession *session, const gchar *seq_set, + const gchar *destfolder) +{ + gint ok; + gchar *destfolder_; + + g_return_val_if_fail(destfolder != NULL, IMAP_ERROR); + + QUOTE_IF_REQUIRED(destfolder_, destfolder); + ok = imap_cmd_gen_send(session, "UID COPY %s %s", seq_set, destfolder_); + if (ok == IMAP_SUCCESS) + ok = imap_cmd_ok(session, NULL); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't copy %s to %s\n"), seq_set, destfolder_); + return -1; + } + + return ok; +} + +gint imap_cmd_envelope(IMAPSession *session, const gchar *seq_set) +{ + return imap_cmd_gen_send + (session, "UID FETCH %s (UID FLAGS RFC822.SIZE RFC822.HEADER)", + seq_set); +} + +static gint imap_cmd_store(IMAPSession *session, const gchar *seq_set, + const gchar *sub_cmd) +{ + gint ok; + + ok = imap_cmd_gen_send(session, "UID STORE %s %s", seq_set, sub_cmd); + if (ok == IMAP_SUCCESS) + ok = imap_cmd_ok(session, NULL); + if (ok != IMAP_SUCCESS) { + log_warning(_("error while imap command: STORE %s %s\n"), + seq_set, sub_cmd); + return ok; + } + + return IMAP_SUCCESS; +} + +static gint imap_cmd_expunge(IMAPSession *session) +{ + gint ok; + + ok = imap_cmd_gen_send(session, "EXPUNGE"); + if (ok == IMAP_SUCCESS) + ok = imap_cmd_ok(session, NULL); + if (ok != IMAP_SUCCESS) { + log_warning(_("error while imap command: EXPUNGE\n")); + return ok; + } + + return IMAP_SUCCESS; +} + +static gint imap_cmd_close(IMAPSession *session) +{ + gint ok; + + ok = imap_cmd_gen_send(session, "CLOSE"); + if (ok == IMAP_SUCCESS) + ok = imap_cmd_ok(session, NULL); + if (ok != IMAP_SUCCESS) + log_warning(_("error while imap command: CLOSE\n")); + + return ok; +} + +static gint imap_cmd_ok_real(IMAPSession *session, GPtrArray *argbuf) +{ + gint ok; + gchar *buf; + gint cmd_num; + gchar cmd_status[IMAPBUFSIZE + 1]; + GString *str; + gchar *p; + gchar obuf[32]; + gint len; + gchar *literal; + + str = g_string_sized_new(256); + + //g_usleep(800000); + while ((ok = imap_cmd_gen_recv(session, &buf)) == IMAP_SUCCESS) { + g_string_append(str, buf); + + if ((p = strrchr_with_skip_quote(buf, '"', '{'))) { + /* literal */ + p = strchr_cpy(p + 1, '}', obuf, sizeof(obuf)); + len = atoi(obuf); + if (len < 0 || p == NULL || *p != '\0') { + g_free(buf); + ok = IMAP_ERROR; + break; + } + + literal = recv_bytes(SESSION(session)->sock, len); + if (!literal) { + g_free(buf); + ok = IMAP_SOCKET; + break; + } + if (memchr(literal, '\n', len)) + log_print("IMAP4< (literal: %d bytes)\n", len); + else + log_print("IMAP4< %s\n", literal); + + g_string_append(str, "\r\n"); + g_string_append_len(str, literal, len); + g_free(literal); + g_free(buf); + continue; + } + + g_free(buf); + + if (str->str[0] == '*' && str->str[1] == ' ') { + if (argbuf) + g_ptr_array_add(argbuf, g_strdup(str->str + 2)); + + g_string_truncate(str, 0); + continue; + } else if (sscanf(str->str, "%d %" Xstr(IMAPBUFSIZE) "s", + &cmd_num, cmd_status) < 2) { + ok = IMAP_ERROR; + } else if (cmd_num == session->cmd_count && + !strcmp(cmd_status, "OK")) { + if (argbuf) + g_ptr_array_add(argbuf, g_strdup(str->str)); + } else { + ok = IMAP_ERROR; + } + + break; + } + + g_string_free(str, TRUE); + return ok; +} + +#if USE_THREADS +static gint imap_cmd_ok_func(IMAPSession *session, gpointer data) +{ + GPtrArray *argbuf = (GPtrArray *)data; + gint ok; + + ok = imap_cmd_ok_real(session, argbuf); + return ok; +} +#endif + +static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf) +{ +#if USE_THREADS + return imap_thread_run(session, imap_cmd_ok_func, argbuf); +#else + return imap_cmd_ok_real(session, argbuf); +#endif +} + +static gint imap_cmd_gen_send(IMAPSession *session, const gchar *format, ...) +{ + IMAPRealSession *real = (IMAPRealSession *)session; + gchar buf[IMAPBUFSIZE]; + gchar tmp[IMAPBUFSIZE]; + gchar *p; + va_list args; + + va_start(args, format); + g_vsnprintf(tmp, sizeof(tmp), format, args); + va_end(args); + +#if USE_THREADS + if (real->is_running) { + g_warning("imap_cmd_gen_send: cannot send command because another command is already running."); + return IMAP_EAGAIN; + } +#endif + + session->cmd_count++; + + g_snprintf(buf, sizeof(buf), "%d %s\r\n", session->cmd_count, tmp); + if (!g_ascii_strncasecmp(tmp, "LOGIN ", 6) && + (p = strchr(tmp + 6, ' '))) { + *p = '\0'; + log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp); + } else + log_print("IMAP4> %d %s\n", session->cmd_count, tmp); + + sock_write_all(SESSION(session)->sock, buf, strlen(buf)); + + return IMAP_SUCCESS; +} + +static gint imap_cmd_gen_recv(IMAPSession *session, gchar **ret) +{ + gint len; + + if ((len = sock_getline(SESSION(session)->sock, ret)) < 0) + return IMAP_SOCKET; + + strretchomp(*ret); + + if (len > 1000) { + gchar *str; + + str = trim_string(*ret, 1000); + log_print("IMAP4< %s\n", str); + g_free(str); + } else + log_print("IMAP4< %s\n", *ret); + + session_set_access_time(SESSION(session)); + + return IMAP_SUCCESS; +} + +static gint imap_cmd_gen_recv_silent(IMAPSession *session, gchar **ret) +{ + gint len; + + if ((len = sock_getline(SESSION(session)->sock, ret)) < 0) + return IMAP_SOCKET; + + strretchomp(*ret); + + session_set_access_time(SESSION(session)); + + return IMAP_SUCCESS; +} + + +/* misc utility functions */ + +static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len) +{ + gchar *tmp; + + dest[0] = '\0'; + tmp = strchr(src, ch); + if (!tmp) + return NULL; + + memcpy(dest, src, MIN(tmp - src, len - 1)); + dest[MIN(tmp - src, len - 1)] = '\0'; + + return tmp + 1; +} + +static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len) +{ + const gchar *p = src; + gint n = 0; + + g_return_val_if_fail(*p == ch, NULL); + + *dest = '\0'; + p++; + + while (*p != '\0' && *p != ch) { + if (n < len - 1) { + if (*p == '\\' && *(p + 1) != '\0') + p++; + *dest++ = *p++; + } else + p++; + n++; + } + + *dest = '\0'; + return (gchar *)(*p == ch ? p + 1 : p); +} + +static gchar *search_array_contain_str(GPtrArray *array, gchar *str) +{ + gint i; + + for (i = 0; i < array->len; i++) { + gchar *tmp; + + tmp = g_ptr_array_index(array, i); + if (strstr(tmp, str) != NULL) + return tmp; + } + + return NULL; +} + +static gchar *search_array_str(GPtrArray *array, gchar *str) +{ + gint i; + gint len; + + len = strlen(str); + + for (i = 0; i < array->len; i++) { + gchar *tmp; + + tmp = g_ptr_array_index(array, i); + if (!strncmp(tmp, str, len)) + return tmp; + } + + return NULL; +} + +static void imap_path_separator_subst(gchar *str, gchar separator) +{ + gchar *p; + gboolean in_escape = FALSE; + + if (!separator || separator == '/') return; + + for (p = str; *p != '\0'; p++) { + if (*p == '/' && !in_escape) + *p = separator; + else if (*p == '&' && *(p + 1) != '-' && !in_escape) + in_escape = TRUE; + else if (*p == '-' && in_escape) + in_escape = FALSE; + } +} + +static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str) +{ + static iconv_t cd = (iconv_t)-1; + static gboolean iconv_ok = TRUE; + GString *norm_utf7; + gchar *norm_utf7_p; + size_t norm_utf7_len; + const gchar *p; + gchar *to_str, *to_p; + size_t to_len; + gboolean in_escape = FALSE; + + if (!iconv_ok) return g_strdup(mutf7_str); + + if (cd == (iconv_t)-1) { + cd = iconv_open(CS_INTERNAL, CS_UTF_7); + if (cd == (iconv_t)-1) { + g_warning("iconv cannot convert UTF-7 to %s\n", + CS_INTERNAL); + iconv_ok = FALSE; + return g_strdup(mutf7_str); + } + } + + /* modified UTF-7 to normal UTF-7 conversion */ + norm_utf7 = g_string_new(NULL); + + for (p = mutf7_str; *p != '\0'; p++) { + /* replace: '&' -> '+', + "&-" -> '&', + "+" -> "+-", + escaped ',' -> '/' */ + if (!in_escape && *p == '&') { + if (*(p + 1) != '-') { + g_string_append_c(norm_utf7, '+'); + in_escape = TRUE; + } else { + g_string_append_c(norm_utf7, '&'); + p++; + } + } else if (!in_escape && *p == '+') { + g_string_append(norm_utf7, "+-"); + } else if (in_escape && *p == ',') { + g_string_append_c(norm_utf7, '/'); + } else if (in_escape && *p == '-') { + g_string_append_c(norm_utf7, '-'); + in_escape = FALSE; + } else { + g_string_append_c(norm_utf7, *p); + } + } + + /* somehow iconv() returns error when the last of the string is "+-" */ + g_string_append_c(norm_utf7, '\n'); + norm_utf7_p = norm_utf7->str; + norm_utf7_len = norm_utf7->len; + to_len = strlen(mutf7_str) * 5; + to_p = to_str = g_malloc(to_len + 1); + + if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len, + &to_p, &to_len) == -1) { + g_warning(_("iconv cannot convert UTF-7 to %s\n"), CS_INTERNAL); + g_string_free(norm_utf7, TRUE); + g_free(to_str); + return g_strdup(mutf7_str); + } + + /* second iconv() call for flushing */ + iconv(cd, NULL, NULL, &to_p, &to_len); + g_string_free(norm_utf7, TRUE); + *to_p = '\0'; + strretchomp(to_str); + + return to_str; +} + +static gchar *imap_utf8_to_modified_utf7(const gchar *from) +{ + static iconv_t cd = (iconv_t)-1; + static gboolean iconv_ok = TRUE; + gchar *norm_utf7, *norm_utf7_p; + size_t from_len, norm_utf7_len; + GString *to_str; + const gchar *from_tmp; + const gchar *p; + gchar *to; + gboolean in_escape = FALSE; + + if (!iconv_ok) return g_strdup(from); + + if (cd == (iconv_t)-1) { + cd = iconv_open(CS_UTF_7, CS_INTERNAL); + if (cd == (iconv_t)-1) { + g_warning(_("iconv cannot convert %s to UTF-7\n"), + CS_INTERNAL); + iconv_ok = FALSE; + return g_strdup(from); + } + } + + /* UTF-8 to normal UTF-7 conversion */ + from_tmp = from; + from_len = strlen(from); + norm_utf7_len = from_len * 5; + norm_utf7 = g_malloc(norm_utf7_len + 1); + norm_utf7_p = norm_utf7; + + while (from_len > 0) { + if (*from_tmp == '+') { + *norm_utf7_p++ = '+'; + *norm_utf7_p++ = '-'; + norm_utf7_len -= 2; + from_tmp++; + from_len--; + } else if (g_ascii_isprint(*from_tmp)) { + /* printable ascii char */ + *norm_utf7_p = *from_tmp; + norm_utf7_p++; + norm_utf7_len--; + from_tmp++; + from_len--; + } else { + size_t conv_len = 0; + + /* unprintable char: convert to UTF-7 */ + p = from_tmp; + while (!g_ascii_isprint(*p) && conv_len < from_len) { + conv_len += g_utf8_skip[*(guchar *)p]; + p += g_utf8_skip[*(guchar *)p]; + } + + from_len -= conv_len; + if (iconv(cd, (ICONV_CONST gchar **)&from_tmp, + &conv_len, + &norm_utf7_p, &norm_utf7_len) == -1) { + g_warning("iconv cannot convert %s to UTF-7\n", + CS_INTERNAL); + g_free(norm_utf7); + return g_strdup(from); + } + + /* second iconv() call for flushing */ + iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len); + } + } + + *norm_utf7_p = '\0'; + to_str = g_string_new(NULL); + for (p = norm_utf7; p < norm_utf7_p; p++) { + /* replace: '&' -> "&-", + '+' -> '&', + "+-" -> '+', + BASE64 '/' -> ',' */ + if (!in_escape && *p == '&') { + g_string_append(to_str, "&-"); + } else if (!in_escape && *p == '+') { + if (*(p + 1) == '-') { + g_string_append_c(to_str, '+'); + p++; + } else { + g_string_append_c(to_str, '&'); + in_escape = TRUE; + } + } else if (in_escape && *p == '/') { + g_string_append_c(to_str, ','); + } else if (in_escape && *p == '-') { + g_string_append_c(to_str, '-'); + in_escape = FALSE; + } else { + g_string_append_c(to_str, *p); + } + } + + if (in_escape) { + in_escape = FALSE; + g_string_append_c(to_str, '-'); + } + + to = g_string_free(to_str, FALSE); + g_free(norm_utf7); + + return to; +} + +static GSList *imap_get_seq_set_from_msglist(GSList *msglist, gint limit) +{ + GString *str; + GSList *sorted_list, *cur; + guint first, last, next; + gchar *ret_str; + GSList *ret_list = NULL; + gint count = 0; + + if (msglist == NULL) + return NULL; + + str = g_string_sized_new(256); + + sorted_list = g_slist_copy(msglist); + sorted_list = procmsg_sort_msg_list(sorted_list, SORT_BY_NUMBER, + SORT_ASCENDING); + + first = ((MsgInfo *)sorted_list->data)->msgnum; + + for (cur = sorted_list; cur != NULL; cur = cur->next) { + ++count; + last = ((MsgInfo *)cur->data)->msgnum; + if (cur->next) + next = ((MsgInfo *)cur->next->data)->msgnum; + else + next = 0; + + if (limit > 0 && count >= limit) { + if (str->len > 0) + g_string_append_c(str, ','); + if (first == last) + g_string_sprintfa(str, "%u", first); + else + g_string_sprintfa(str, "%u:%u", first, last); + + first = next; + + ret_str = g_strdup(str->str); + ret_list = g_slist_append(ret_list, ret_str); + g_string_truncate(str, 0); + count = 0; + continue; + } + + if (last + 1 != next || next == 0) { + if (str->len > 0) + g_string_append_c(str, ','); + if (first == last) + g_string_sprintfa(str, "%u", first); + else + g_string_sprintfa(str, "%u:%u", first, last); + + first = next; + + if (str->len > IMAP_CMD_LIMIT) { + ret_str = g_strdup(str->str); + ret_list = g_slist_append(ret_list, ret_str); + g_string_truncate(str, 0); + } + } + } + + if (str->len > 0) { + ret_str = g_strdup(str->str); + ret_list = g_slist_append(ret_list, ret_str); + } + + g_slist_free(sorted_list); + g_string_free(str, TRUE); + + return ret_list; +} + +static gint imap_seq_set_get_count(const gchar *seq_set) +{ + gint count = 0; + guint first, last; + gchar *tmp, *p, *q; + + p = q = tmp = g_strdup(seq_set); + + while (*p) { + if (*p == ',') { + *p = '\0'; + if (sscanf(q, "%u:%u", &first, &last) == 2) + count += last - first + 1; + else if (sscanf(q, "%u", &first) == 1) + count++; + q = ++p; + } else + ++p; + } + if (q != p) { + if (sscanf(q, "%u:%u", &first, &last) == 2) + count += last - first + 1; + else if (sscanf(q, "%u", &first) == 1) + count++; + } + + g_free(tmp); + + return count; +} + +static void imap_seq_set_free(GSList *seq_list) +{ + slist_free_strings(seq_list); + g_slist_free(seq_list); +} + +static GHashTable *imap_get_uid_table(GArray *array) +{ + GHashTable *table; + gint i; + guint32 uid; + + g_return_val_if_fail(array != NULL, NULL); + + table = g_hash_table_new(NULL, g_direct_equal); + + for (i = 0; i < array->len; i++) { + uid = g_array_index(array, guint32, i); + g_hash_table_insert(table, GUINT_TO_POINTER(uid), + GINT_TO_POINTER(i + 1)); + } + + return table; +} + +static gboolean imap_rename_folder_func(GNode *node, gpointer data) +{ + FolderItem *item = node->data; + gchar **paths = data; + const gchar *oldpath = paths[0]; + const gchar *newpath = paths[1]; + gchar *base; + gchar *new_itempath; + gint oldpathlen; + + oldpathlen = strlen(oldpath); + if (strncmp(oldpath, item->path, oldpathlen) != 0) { + g_warning("path doesn't match: %s, %s\n", oldpath, item->path); + return TRUE; + } + + base = item->path + oldpathlen; + while (*base == '/') base++; + if (*base == '\0') + new_itempath = g_strdup(newpath); + else + new_itempath = g_strconcat(newpath, "/", base, NULL); + g_free(item->path); + item->path = new_itempath; + + return FALSE; +} + +#if USE_THREADS +static void imap_thread_run_proxy(gpointer push_data, gpointer data) +{ + IMAPRealSession *real = (IMAPRealSession *)data; + + debug_print("imap_thread_run_proxy (%p): calling thread_func\n", g_thread_self()); + real->retval = real->thread_func(IMAP_SESSION(real), real->thread_data); + g_atomic_int_set(&real->flag, 1); + g_main_context_wakeup(NULL); + debug_print("imap_thread_run_proxy (%p): thread_func done\n", g_thread_self()); +} + +static gint imap_thread_run(IMAPSession *session, IMAPThreadFunc func, + gpointer data) +{ + IMAPRealSession *real = (IMAPRealSession *)session; + gint ret; + + if (real->is_running) { + g_warning("imap_thread_run: thread is already running"); + return IMAP_ERROR; + } + + if (!real->pool) { + real->pool = g_thread_pool_new(imap_thread_run_proxy, real, + -1, FALSE, NULL); + if (!real->pool) + return IMAP_ERROR; + } + + real->is_running = TRUE; + real->thread_func = func; + real->thread_data = data; + real->flag = 0; + real->retval = 0; + + g_thread_pool_push(real->pool, real, NULL); + + while (g_atomic_int_get(&real->flag) == 0) + event_loop_iterate(); + + real->is_running = FALSE; + real->thread_func = NULL; + real->thread_data = NULL; + real->flag = 0; + ret = real->retval; + real->retval = 0; + log_flush(); + + return ret; +} + +static gint imap_thread_run_progress(IMAPSession *session, IMAPThreadFunc func, + IMAPProgressFunc progress_func, + gpointer data) +{ + IMAPRealSession *real = (IMAPRealSession *)session; + gint prev_count = 0; + gint ret; + + if (real->is_running) { + g_warning("imap_thread_run: thread is already running"); + return IMAP_ERROR; + } + + if (!real->pool) { + real->pool = g_thread_pool_new(imap_thread_run_proxy, real, + -1, FALSE, NULL); + if (!real->pool) + return IMAP_ERROR; + } + + real->is_running = TRUE; + real->thread_func = func; + real->thread_data = data; + real->flag = 0; + real->retval = 0; + real->prog_count = 0; + real->prog_total = 0; + + g_thread_pool_push(real->pool, real, NULL); + + while (g_atomic_int_get(&real->flag) == 0) { + event_loop_iterate(); + if (prev_count != real->prog_count && real->prog_total > 0) { + progress_func(session, real->prog_count, + real->prog_total, data); + prev_count = real->prog_count; + } + } + + real->is_running = FALSE; + real->thread_func = NULL; + real->thread_data = NULL; + real->flag = 0; + ret = real->retval; + real->retval = 0; + real->prog_count = 0; + real->prog_total = 0; + log_flush(); + + return ret; +} +#endif /* USE_THREADS */ + +gboolean imap_is_session_active(IMAPFolder *folder) +{ +#if USE_THREADS + IMAPRealSession *real; + + g_return_val_if_fail(folder != NULL, FALSE); + + real = (IMAPRealSession *)(REMOTE_FOLDER(folder)->session); + if (!real) + return FALSE; + + return real->is_running; +#else + return FALSE; +#endif +} diff --git a/libsylph/imap.h b/libsylph/imap.h new file mode 100644 index 0000000..ac928b4 --- /dev/null +++ b/libsylph/imap.h @@ -0,0 +1,130 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2011 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __IMAP_H__ +#define __IMAP_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "folder.h" +#include "session.h" +#include "procmsg.h" + +typedef struct _IMAPFolder IMAPFolder; +typedef struct _IMAPSession IMAPSession; +typedef struct _IMAPNameSpace IMAPNameSpace; + +#define IMAP_FOLDER(obj) ((IMAPFolder *)obj) +#define IMAP_SESSION(obj) ((IMAPSession *)obj) + +#include "prefs_account.h" + +typedef enum +{ + IMAP_AUTH_LOGIN = 1 << 0, + IMAP_AUTH_CRAM_MD5 = 1 << 1, + IMAP_AUTH_PLAIN = 1 << 2 +} IMAPAuthType; + +struct _IMAPFolder +{ + RemoteFolder rfolder; + + /* list of IMAPNameSpace */ + GList *ns_personal; + GList *ns_others; + GList *ns_shared; +}; + +struct _IMAPSession +{ + Session session; + + gboolean authenticated; + + gchar **capability; + gboolean uidplus; + + gchar *mbox; + guint cmd_count; +}; + +struct _IMAPNameSpace +{ + gchar *name; + gchar separator; +}; + +#define IMAP_SUCCESS 0 +#define IMAP_SOCKET 2 +#define IMAP_AUTHFAIL 3 +#define IMAP_PROTOCOL 4 +#define IMAP_SYNTAX 5 +#define IMAP_IOERR 6 +#define IMAP_ERROR 7 +#define IMAP_EAGAIN 8 + +#define IMAPBUFSIZE 8192 + +typedef enum +{ + IMAP_FLAG_SEEN = 1 << 0, + IMAP_FLAG_ANSWERED = 1 << 1, + IMAP_FLAG_FLAGGED = 1 << 2, + IMAP_FLAG_DELETED = 1 << 3, + IMAP_FLAG_DRAFT = 1 << 4, + + /* color label keywords : 1 << 7 ... 1 << 9 + compatible with procmsg.h: MSG_CLABEL* macros */ +} IMAPFlags; + +#define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0) +#define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0) +#define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0) +#define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0) +#define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0) + +#define IMAP_GET_COLORLABEL(flags) (flags & (7 << MSG_CLABEL_SBIT)) +#define IMAP_GET_COLORLABEL_VALUE(flags) \ + (IMAP_GET_COLORLABEL(flags) >> MSG_CLABEL_SBIT) +#define IMAP_SET_COLORLABEL_VALUE(flags, v) \ + ((flags) |= ((v & 7) << MSG_CLABEL_SBIT)) + +FolderClass *imap_get_class (void); + +gint imap_msg_set_perm_flags (MsgInfo *msginfo, + MsgPermFlags flags); +gint imap_msg_unset_perm_flags (MsgInfo *msginfo, + MsgPermFlags flags); +gint imap_msg_list_set_perm_flags (GSList *msglist, + MsgPermFlags flags); +gint imap_msg_list_unset_perm_flags (GSList *msglist, + MsgPermFlags flags); + +gint imap_msg_list_set_colorlabel_flags (GSList *msglist, + guint color); + +gboolean imap_is_session_active (IMAPFolder *folder); + +#endif /* __IMAP_H__ */ diff --git a/libsylph/libsylph-0.def b/libsylph/libsylph-0.def new file mode 100644 index 0000000..f625f1f --- /dev/null +++ b/libsylph/libsylph-0.def @@ -0,0 +1,714 @@ +EXPORTS + account_address_exist @ 1 + account_append @ 2 + account_destroy @ 3 + account_find_from_address @ 4 + account_find_from_id @ 5 + account_find_from_item @ 6 + account_find_from_item_property @ 7 + account_find_from_message_file @ 8 + account_find_from_msginfo @ 9 + account_find_from_smtp_server @ 10 + account_foreach @ 11 + account_get_current_account @ 12 + account_get_default @ 13 + account_get_list @ 14 + account_get_special_folder @ 15 + account_list_free @ 16 + account_read_config_all @ 17 + account_set_as_default @ 18 + account_update_lock @ 19 + account_update_unlock @ 20 + account_updated @ 21 + account_write_config_all @ 22 + add_history @ 23 + address_equal @ 24 + address_list_append @ 25 + address_list_append_orig @ 26 + address_table @ 27 DATA + base64_decode @ 28 + base64_decoder_decode @ 29 + base64_decoder_free @ 30 + base64_decoder_new @ 31 + base64_encode @ 32 + canonicalize_file @ 33 + canonicalize_file_replace @ 34 + canonicalize_file_stream @ 35 + canonicalize_str @ 36 + change_dir @ 37 + change_file_mode_rw @ 38 + check_line_length @ 39 + close_log_file @ 40 + conv_check_file_encoding @ 41 + conv_code_converter_destroy @ 42 + conv_code_converter_new @ 43 + conv_codeset_strdup_full @ 44 + conv_convert @ 45 + conv_copy_dir @ 46 + conv_copy_file @ 47 + conv_encode_filename @ 48 + conv_encode_header @ 49 + conv_filename_from_utf8 @ 50 + conv_filename_to_utf8 @ 51 + conv_get_autodetect_type @ 52 + conv_get_charset_from_str @ 53 + conv_get_charset_str @ 54 + conv_get_code_conv_func @ 55 + conv_get_current_locale @ 56 + conv_get_internal_charset @ 57 + conv_get_internal_charset_str @ 58 + conv_get_locale_charset @ 59 + conv_get_locale_charset_str @ 60 + conv_get_outgoing_charset @ 61 + conv_get_outgoing_charset_str @ 62 + conv_guess_ja_encoding @ 63 + conv_iconv_strdup @ 64 + conv_iconv_strdup_with_cd @ 65 + conv_is_ja_locale @ 66 + conv_is_multibyte_encoding @ 67 + conv_localetodisp @ 68 + conv_mb_alnum @ 69 + conv_set_autodetect_type @ 70 + conv_unmime_header @ 71 + conv_utf8todisp @ 72 + copy_dir @ 73 + copy_file @ 74 + copy_file_part @ 75 + copy_mbox @ 76 + cur_account @ 77 DATA + custom_header_find @ 78 + custom_header_free @ 79 + custom_header_get_str @ 80 + custom_header_read_config @ 81 + custom_header_read_str @ 82 + custom_header_write_config @ 83 + debug_print @ 84 + decode_uri @ 85 + decode_xdigit_encoded_str @ 86 + dirent_is_directory @ 87 + dirent_is_regular_file @ 88 + display_header_prop_free @ 89 + display_header_prop_get_str @ 90 + display_header_prop_read_str @ 91 + eliminate_address_comment @ 92 + eliminate_parenthesis @ 93 + eliminate_quote @ 94 + empty_mbox @ 95 + encode_uri @ 96 + event_loop_iterate @ 97 + execute_async @ 98 + execute_command_line @ 99 + execute_command_line_async_wait @ 100 + execute_open_file @ 101 + execute_print_file @ 102 + execute_sync @ 103 + export_to_mbox @ 104 + extract_address @ 105 + extract_list_id_str @ 106 + extract_parenthesis @ 107 + extract_parenthesis_with_escape @ 108 + extract_parenthesis_with_skip_quote @ 109 + extract_quote @ 110 + extract_quote_with_escape @ 111 + fd_accept @ 112 + fd_close @ 113 + fd_connect_inet @ 114 + fd_connect_unix @ 115 + fd_getline @ 116 + fd_gets @ 117 + fd_open_inet @ 118 + fd_open_unix @ 119 + fd_read @ 120 + fd_recv @ 121 + fd_write @ 122 + fd_write_all @ 123 + file_exist @ 124 + file_read_stream_to_str @ 125 + file_read_to_str @ 126 + filter_action_exec @ 127 + filter_action_list_free @ 128 + filter_action_new @ 129 + filter_apply @ 130 + filter_apply_msginfo @ 131 + filter_cond_list_free @ 132 + filter_cond_new @ 133 + filter_get_keyword_from_msg @ 134 + filter_get_str @ 135 + filter_info_free @ 136 + filter_info_new @ 137 + filter_list_delete_path @ 138 + filter_list_rename_path @ 139 + filter_match_rule @ 140 + filter_read_config @ 141 + filter_read_file @ 142 + filter_read_str @ 143 + filter_rule_delete_action_by_dest_path @ 144 + filter_rule_free @ 145 + filter_rule_list_free @ 146 + filter_rule_match_type_str_to_enum @ 147 + filter_rule_new @ 148 + filter_rule_rename_dest_path @ 149 + filter_rule_requires_full_headers @ 150 + filter_write_config @ 151 + filter_write_file @ 152 + filter_xml_node_to_filter_list @ 153 + folder_add @ 154 + folder_create_tree @ 155 + folder_destroy @ 156 + folder_find_child_item_by_name @ 157 + folder_find_from_name @ 158 + folder_find_from_path @ 159 + folder_find_item_and_num_from_id @ 160 + folder_find_item_from_identifier @ 161 + folder_find_item_from_path @ 162 + folder_get_default_draft @ 163 + folder_get_default_folder @ 164 + folder_get_default_inbox @ 165 + folder_get_default_outbox @ 166 + folder_get_default_queue @ 167 + folder_get_default_trash @ 168 + folder_get_identifier @ 169 + folder_get_list @ 170 + folder_get_path @ 171 + folder_get_status @ 172 + folder_item_add_msg @ 173 + folder_item_add_msg_msginfo @ 174 + folder_item_add_msgs @ 175 + folder_item_add_msgs_msginfo @ 176 + folder_item_append @ 177 + folder_item_close @ 178 + folder_item_compare @ 179 + folder_item_copy @ 180 + folder_item_copy_msg @ 181 + folder_item_copy_msgs @ 182 + folder_item_destroy @ 183 + folder_item_fetch_all_msg @ 184 + folder_item_fetch_msg @ 185 + folder_item_get_cache_file @ 186 + folder_item_get_identifier @ 187 + folder_item_get_mark_file @ 188 + folder_item_get_msg_list @ 189 + folder_item_get_msginfo @ 190 + folder_item_get_path @ 191 + folder_item_get_uncached_msg_list @ 192 + folder_item_is_msg_changed @ 193 + folder_item_move_msg @ 194 + folder_item_move_msgs @ 195 + folder_item_new @ 196 + folder_item_remove @ 197 + folder_item_remove_all_msg @ 198 + folder_item_remove_children @ 199 + folder_item_remove_msg @ 200 + folder_item_remove_msgs @ 201 + folder_item_scan @ 202 + folder_item_scan_foreach @ 203 + folder_local_folder_destroy @ 204 + folder_local_folder_init @ 205 + folder_new @ 206 + folder_read_list @ 207 + folder_remote_folder_destroy @ 208 + folder_remote_folder_init @ 209 + folder_scan_tree @ 210 + folder_set_missing_folders @ 211 + folder_set_name @ 212 + folder_set_ui_func @ 213 + folder_tree_destroy @ 214 + folder_unref_account_all @ 215 + folder_write_list @ 216 + fromuutobits @ 217 + generate_mime_boundary @ 218 + get_abbrev_newsgroup_name @ 219 + get_alt_filename @ 220 + get_command_output @ 221 + get_debug_mode @ 222 + get_document_dir @ 223 + get_domain_name @ 224 + get_file_size @ 225 + get_file_size_as_crlf @ 226 + get_home_dir @ 227 + get_imap_cache_dir @ 228 + get_left_file_size @ 229 + get_mail_base_dir @ 230 + get_mime_tmp_dir @ 231 + get_news_cache_dir @ 232 + get_next_word_len @ 233 + get_old_rc_dir @ 234 + get_outgoing_rfc2822_file @ 235 + get_outgoing_rfc2822_str @ 236 + get_quote_level @ 237 + get_rc_dir @ 238 + get_rfc822_date @ 239 + get_startup_dir @ 240 + get_template_dir @ 241 + get_tmp_dir @ 242 + get_tmp_file @ 243 + get_uri_len @ 244 + get_uri_path @ 245 + hash_free_strings @ 246 + hash_free_value_mem @ 247 + html_parse @ 248 + html_parser_destroy @ 249 + html_parser_new @ 250 + imap_get_class @ 251 + imap_msg_list_set_perm_flags @ 252 + imap_msg_list_unset_perm_flags @ 253 + imap_msg_set_perm_flags @ 254 + imap_msg_unset_perm_flags @ 255 + input_query_password @ 256 + is_ascii_str @ 257 + is_dir_exist @ 258 + is_file_entry_exist @ 259 + is_header_line @ 260 + is_next_nonascii @ 261 + is_uri_string @ 262 + itos @ 263 + itos_buf @ 264 + list_free_strings @ 265 + lock_mbox @ 266 + log_error @ 267 + log_flush @ 268 + log_message @ 269 + log_print @ 270 + log_warning @ 271 + log_write @ 272 + make_dir @ 273 + make_dir_hier @ 274 + md5_hex_hmac @ 275 + md5_hmac @ 276 + mh_get_class @ 277 + move_file @ 278 + my_gethostbyname @ 279 + my_memmem @ 280 + my_strftime @ 281 + my_tmpfile @ 282 + news_get_class @ 283 + news_get_group_list @ 284 + news_group_list_free @ 285 + news_post @ 286 + news_post_stream @ 287 + news_remove_group_list_cache @ 288 + newsgroup_list_append @ 289 + nntp_article @ 290 + nntp_body @ 291 + nntp_get_article @ 292 + nntp_group @ 293 + nntp_head @ 294 + nntp_list @ 295 + nntp_mode @ 296 + nntp_newgroups @ 297 + nntp_newnews @ 298 + nntp_next @ 299 + nntp_post @ 300 + nntp_session_new @ 301 + nntp_stat @ 302 + nntp_xhdr @ 303 + nntp_xover @ 304 + normalize_address_field @ 305 + normalize_newlines @ 306 + open_uri @ 307 + path_cmp @ 308 + pop3_delete_recv @ 309 + pop3_delete_send @ 310 + pop3_gen_send @ 311 + pop3_get_uidl_table @ 312 + pop3_getauth_apop_send @ 313 + pop3_getauth_pass_send @ 314 + pop3_getauth_user_send @ 315 + pop3_getrange_last_recv @ 316 + pop3_getrange_last_send @ 317 + pop3_getrange_stat_recv @ 318 + pop3_getrange_stat_send @ 319 + pop3_getrange_uidl_recv @ 320 + pop3_getrange_uidl_send @ 321 + pop3_getsize_list_recv @ 322 + pop3_getsize_list_send @ 323 + pop3_greeting_recv @ 324 + pop3_logout_send @ 325 + pop3_ok @ 326 + pop3_retr_recv @ 327 + pop3_retr_send @ 328 + pop3_session_new @ 329 + pop3_stls_recv @ 330 + pop3_stls_send @ 331 + pop3_write_msg_to_file @ 332 + pop3_write_uidl_list @ 333 + prefs_account_apply_tmp_prefs @ 334 + prefs_account_free @ 335 + prefs_account_get_params @ 336 + prefs_account_get_tmp_prefs @ 337 + prefs_account_new @ 338 + prefs_account_read_config @ 339 + prefs_account_set_tmp_prefs @ 340 + prefs_account_write_config_all @ 341 + prefs_common @ 342 DATA + prefs_common_get @ 343 + prefs_common_get_params @ 344 + prefs_common_junk_filter_list_set @ 345 + prefs_common_junk_folder_rename_path @ 346 + prefs_common_read_config @ 347 + prefs_common_write_config @ 348 + prefs_file_close @ 349 + prefs_file_close_revert @ 350 + prefs_file_get_backup_generation @ 351 + prefs_file_open @ 352 + prefs_file_set_backup_generation @ 353 + prefs_file_write_param @ 354 + prefs_free @ 355 + prefs_param_table_destroy @ 356 + prefs_param_table_get @ 357 + prefs_read_config @ 358 + prefs_set_default @ 359 + prefs_write_config @ 360 + proc_mbox @ 361 + proc_mbox_full @ 362 + procheader_add_header_list @ 363 + procheader_copy_header_list @ 364 + procheader_date_get_localtime @ 365 + procheader_date_parse @ 366 + procheader_find_header_list @ 367 + procheader_get_fromname @ 368 + procheader_get_header_array @ 369 + procheader_get_header_array_asis @ 370 + procheader_get_header_array_for_display @ 371 + procheader_get_header_fields @ 372 + procheader_get_header_list @ 373 + procheader_get_header_list_from_file @ 374 + procheader_get_header_list_from_msginfo @ 375 + procheader_get_one_field @ 376 + procheader_get_toname @ 377 + procheader_get_unfolded_line @ 378 + procheader_header_array_destroy @ 379 + procheader_header_free @ 380 + procheader_header_list_destroy @ 381 + procheader_merge_header_list @ 382 + procheader_merge_header_list_dup @ 383 + procheader_parse_file @ 384 + procheader_parse_str @ 385 + procheader_parse_stream @ 386 + procmime_decode_content @ 387 + procmime_execute_open_file @ 388 + procmime_find_string @ 389 + procmime_find_string_part @ 390 + procmime_get_all_parts @ 391 + procmime_get_encoding_for_charset @ 392 + procmime_get_encoding_for_str @ 393 + procmime_get_encoding_for_text_file @ 394 + procmime_get_encoding_str @ 395 + procmime_get_first_text_content @ 396 + procmime_get_mime_type @ 397 + procmime_get_part @ 398 + procmime_get_part_file_name @ 399 + procmime_get_part_fp @ 400 + procmime_get_text_content @ 401 + procmime_get_tmp_file_name @ 402 + procmime_mimeinfo_free_all @ 403 + procmime_mimeinfo_insert @ 404 + procmime_mimeinfo_new @ 405 + procmime_mimeinfo_next @ 406 + procmime_scan_content_disposition @ 407 + procmime_scan_content_type @ 408 + procmime_scan_content_type_str @ 409 + procmime_scan_encoding @ 410 + procmime_scan_message @ 411 + procmime_scan_mime_header @ 412 + procmime_scan_mime_type @ 413 + procmime_scan_multipart_message @ 414 + procmsg_add_cache_queue @ 415 + procmsg_add_flags @ 416 + procmsg_add_mark_queue @ 417 + procmsg_clear_cache @ 418 + procmsg_clear_mark @ 419 + procmsg_cmp_msgnum_for_sort @ 420 + procmsg_copy_messages @ 421 + procmsg_empty_all_trash @ 422 + procmsg_empty_trash @ 423 + procmsg_flush_cache_queue @ 424 + procmsg_flush_folder @ 425 + procmsg_flush_folder_foreach @ 426 + procmsg_flush_mark_queue @ 427 + procmsg_get_last_num_in_msg_list @ 428 + procmsg_get_mark_sum @ 429 + procmsg_get_message_file @ 430 + procmsg_get_message_file_list @ 431 + procmsg_get_message_file_path @ 432 + procmsg_get_msginfo @ 433 + procmsg_get_thread_date @ 434 + procmsg_get_thread_tree @ 435 + procmsg_mark_all_read @ 436 + procmsg_message_file_list_free @ 437 + procmsg_move_messages @ 438 + procmsg_msg_exist @ 439 + procmsg_msg_hash_table_append @ 440 + procmsg_msg_hash_table_create @ 441 + procmsg_msg_list_free @ 442 + procmsg_msginfo_copy @ 443 + procmsg_msginfo_equal @ 444 + procmsg_msginfo_free @ 445 + procmsg_msginfo_get_full_info @ 446 + procmsg_open_cache_file @ 447 + procmsg_open_data_file @ 448 + procmsg_open_mark_file @ 449 + procmsg_open_message @ 450 + procmsg_open_message_decrypted @ 451 + procmsg_print_message @ 452 + procmsg_print_message_part @ 453 + procmsg_read_cache @ 454 + procmsg_read_cache_data_str @ 455 + procmsg_remove_all_cached_messages @ 456 + procmsg_save_to_outbox @ 457 + procmsg_set_auto_decrypt_message @ 458 + procmsg_set_decrypt_message_func @ 459 + procmsg_set_flags @ 460 + procmsg_sort_msg_list @ 461 + procmsg_to_folder_hash_table_create @ 462 + procmsg_trash_messages_exist @ 463 + procmsg_write_cache @ 464 + procmsg_write_cache_list @ 465 + procmsg_write_flags @ 466 + procmsg_write_flags_for_multiple_folders @ 467 + procmsg_write_flags_list @ 468 + progress_show @ 469 + ptr_array_free_strings @ 470 + qp_decode_line @ 471 + qp_decode_q_encoding @ 472 + qp_encode_line @ 473 + qp_get_q_encoding_len @ 474 + qp_q_encode @ 475 + recv_bytes @ 476 + recv_bytes_write @ 477 + recv_bytes_write_to_file @ 478 + recv_set_ui_func @ 479 + recv_write @ 480 + recv_write_to_file @ 481 + references_list_append @ 482 + references_list_prepend @ 483 + remote_tzoffset_sec @ 484 + remove_all_files @ 485 + remove_all_numbered_files @ 486 + remove_dir_recursive @ 487 + remove_expired_files @ 488 + remove_numbered_files @ 489 + remove_return @ 490 + remove_space @ 491 + rename_force @ 492 + s_gnet_md5_clone @ 493 + s_gnet_md5_copy_string @ 494 + s_gnet_md5_delete @ 495 + s_gnet_md5_equal @ 496 + s_gnet_md5_final @ 497 + s_gnet_md5_get_digest @ 498 + s_gnet_md5_get_string @ 499 + s_gnet_md5_hash @ 500 + s_gnet_md5_new @ 501 + s_gnet_md5_new_incremental @ 502 + s_gnet_md5_new_string @ 503 + s_gnet_md5_update @ 504 + scan_mailto_url @ 505 + session_connect @ 506 + session_destroy @ 507 + session_disconnect @ 508 + session_init @ 509 + session_is_connected @ 510 + session_recv_data @ 511 + session_recv_data_as_file @ 512 + session_recv_msg @ 513 + session_send_data @ 514 + session_send_msg @ 515 + session_set_access_time @ 516 + session_set_recv_data_notify @ 517 + session_set_recv_data_progressive_notify @ 518 + session_set_recv_message_notify @ 519 + session_set_send_data_notify @ 520 + session_set_send_data_progressive_notify @ 521 + session_set_timeout @ 522 + session_start_tls @ 523 + set_debug_mode @ 524 + set_event_loop_func @ 525 + set_input_query_password_func @ 526 + set_log_file @ 527 + set_log_show_status_func @ 528 + set_log_ui_func @ 529 + set_log_ui_func_full @ 530 + set_log_verbosity @ 531 + set_progress_func @ 532 + set_rc_dir @ 533 + set_startup_dir @ 534 + set_ui_update_func @ 535 + sinfo_equal @ 536 + sinfo_hash @ 537 + slist_free_strings @ 538 + smtp_session_new @ 539 + sock_add_watch @ 540 + sock_add_watch_poll @ 541 + sock_cleanup @ 542 + sock_close @ 543 + sock_connect @ 544 + sock_connect_async_thread @ 545 + sock_connect_async_thread_wait @ 546 + sock_getline @ 547 + sock_gets @ 548 + sock_has_read_data @ 549 + sock_init @ 550 + sock_is_nonblocking_mode @ 551 + sock_peek @ 552 + sock_printf @ 553 + sock_puts @ 554 + sock_read @ 555 + sock_set_io_timeout @ 556 + sock_set_nonblocking_mode @ 557 + sock_watch_funcs @ 558 DATA + sock_write @ 559 + sock_write_all @ 560 + ssl_done @ 561 + ssl_done_socket @ 562 + ssl_getline @ 563 + ssl_gets @ 564 + ssl_init @ 565 + ssl_init_socket @ 566 + ssl_init_socket_with_method @ 567 + ssl_peek @ 568 + ssl_read @ 569 + ssl_set_verify_func @ 570 + ssl_write @ 571 + ssl_write_all @ 572 + status_print @ 573 + str_case_equal @ 574 + str_case_find @ 575 + str_case_find_equal @ 576 + str_case_hash @ 577 + str_find @ 578 + str_find_equal @ 579 + str_find_format_times @ 580 + str_has_suffix_case @ 581 + str_open_as_stream @ 582 + str_write_to_file @ 583 + strcasestr @ 584 + strchomp_all @ 585 + strchr_parenthesis_close @ 586 + strchr_with_skip_quote @ 587 + strcmp2 @ 588 + strcrchomp @ 589 + string_table_free @ 590 + string_table_free_string @ 591 + string_table_get_stats @ 592 + string_table_insert_string @ 593 + string_table_lookup_string @ 594 + string_table_new @ 595 + strncpy2 @ 596 + strrchr_with_skip_quote @ 597 + strretchomp @ 598 + strsplit_csv @ 599 + strsplit_parenthesis @ 600 + strsplit_with_quote @ 601 + strstr_with_skip_quote @ 602 + strtailchomp @ 603 + subject_compare @ 604 + subject_compare_for_sort @ 605 + subst_char @ 606 + subst_chars @ 607 + subst_control @ 608 + subst_for_filename @ 609 + subst_null @ 610 + syl_app_create @ 611 + syl_app_get @ 612 + syl_app_get_type @ 613 + syl_cleanup @ 614 + syl_init @ 615 + syl_init_gettext @ 616 + syl_link @ 617 + syl_marshal_VOID__POINTER_STRING_STRING @ 618 + syl_marshal_VOID__POINTER_STRING_UINT @ 619 + syl_save_all_state @ 620 + syl_setup_rc_dir @ 621 + to_human_readable @ 622 + to_number @ 623 + touufrombits @ 624 + trim_string @ 625 + trim_string_before @ 626 + trim_subject @ 627 + trim_subject_for_compare @ 628 + trim_subject_for_sort @ 629 + tzoffset @ 630 + tzoffset_buf @ 631 + tzoffset_sec @ 632 + ui_update @ 633 + uncanonicalize_file @ 634 + uncanonicalize_file_replace @ 635 + unfold_line @ 636 + unlock_mbox @ 637 + unmime_header @ 638 + uri_list_extract_filenames @ 639 + uriencode_for_filename @ 640 + uriencode_for_mailto @ 641 + utos_buf @ 642 + uudigit @ 643 DATA + virtual_get_class @ 644 + xml_attr_new @ 645 + xml_close_file @ 646 + xml_compare_tag @ 647 + xml_copy_attr @ 648 + xml_copy_tag @ 649 + xml_file_put_escape_str @ 650 + xml_file_put_node @ 651 + xml_file_put_xml_decl @ 652 + xml_free_node @ 653 + xml_free_tree @ 654 + xml_get_current_tag @ 655 + xml_get_current_tag_attr @ 656 + xml_get_dtd @ 657 + xml_get_element @ 658 + xml_node_new @ 659 + xml_open_file @ 660 + xml_parse_file @ 661 + xml_parse_next_tag @ 662 + xml_pop_tag @ 663 + xml_push_tag @ 664 + xml_read_line @ 665 + xml_tag_add_attr @ 666 + xml_tag_new @ 667 + xml_truncate_buf @ 668 + xml_unescape_str @ 669 + strcasestr_with_skip_quote @ 670 + is_path_parent @ 671 + extract_addresses @ 672 + to_unumber @ 673 + imap_msg_list_set_colorlabel_flags @ 674 + filter_get_addressbook_func @ 675 + filter_set_addressbook_func @ 676 + procmsg_flaginfo_list_free @ 677 + procmsg_concat_partial_messages @ 678 + get_last_empty_line_size @ 679 + append_file_part @ 680 + procmime_scan_content_type_partial @ 681 + folder_get_default_junk @ 682 + folder_get_junk @ 683 + folder_set_junk @ 684 + procmime_get_part_fp_fp @ 685 + xml_escape_str @ 686 + session_connect_full @ 687 + socks_info_new @ 688 + socks_info_free @ 689 + socks_connect @ 690 + socks4_connect @ 691 + socks5_connect @ 692 + folder_remote_folder_destroy_all_sessions @ 693 + filter_junk_rule_create @ 694 + folder_remote_folder_active_session_exist @ 695 + procmsg_add_messages_from_queue @ 696 + to_human_readable_buf @ 697 + folder_item_is_trash @ 698 + play_sound @ 699 + session_get_error @ 700 + sock_new @ 701 + sock_info_connect @ 702 + sock_info_connect_async_thread @ 703 + sock_info_connect_async_thread_wait @ 704 + folder_set_ui_func2 @ 705 + folder_get_ui_func2 @ 706 + folder_call_ui_func2 @ 707 + export_msgs_to_mbox @ 708 + copy_file_stream @ 709 + procmsg_save_message_as_text @ 710 + procmime_get_tmp_file_name_for_user @ 711 + procmime_scan_message_stream @ 712 + strconcat_csv @ 713 diff --git a/libsylph/mbox.c b/libsylph/mbox.c new file mode 100644 index 0000000..4c0b2da --- /dev/null +++ b/libsylph/mbox.c @@ -0,0 +1,572 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2012 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LOCKFILE_H +# include +#endif + +#include "mbox.h" +#include "procmsg.h" +#include "procheader.h" +#include "folder.h" +#include "filter.h" +#include "prefs_common.h" +#include "prefs_account.h" +#include "account.h" +#include "utils.h" + +#define FPUTS_TO_TMP_ABORT_IF_FAIL(s) \ +{ \ + if (fputs(s, tmp_fp) == EOF) { \ + g_warning(_("can't write to temporary file\n")); \ + fclose(tmp_fp); \ + fclose(mbox_fp); \ + g_unlink(tmp_file); \ + g_free(tmp_file); \ + return -1; \ + } \ +} + +gint proc_mbox(FolderItem *dest, const gchar *mbox, GHashTable *folder_table) +{ + return proc_mbox_full(dest, mbox, folder_table, + folder_table ? TRUE : FALSE, + folder_table && prefs_common.enable_junk && + prefs_common.filter_junk_on_recv ? TRUE : FALSE); +} + +gint proc_mbox_full(FolderItem *dest, const gchar *mbox, + GHashTable *folder_table, gboolean apply_filter, + gboolean filter_junk) +{ + FILE *mbox_fp; + gchar buf[BUFFSIZE], from_line[BUFFSIZE]; + gchar *tmp_file; + gint new_msgs = 0; + guint count = 0; + Folder *folder; + FilterRule *junk_rule = NULL; + GSList junk_fltlist = {NULL, NULL}; + FolderItem *junk; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(dest->folder != NULL, -1); + g_return_val_if_fail(mbox != NULL, -1); + + debug_print(_("Getting messages from %s into %s...\n"), mbox, dest->path); + + folder = dest->folder; + + if ((mbox_fp = g_fopen(mbox, "rb")) == NULL) { + FILE_OP_ERROR(mbox, "fopen"); + return -1; + } + + /* ignore empty lines on the head */ + do { + if (fgets(buf, sizeof(buf), mbox_fp) == NULL) { + g_warning(_("can't read mbox file.\n")); + fclose(mbox_fp); + return -1; + } + } while (buf[0] == '\n' || buf[0] == '\r'); + + if (strncmp(buf, "From ", 5) != 0) { + g_warning(_("invalid mbox format: %s\n"), mbox); + fclose(mbox_fp); + return -1; + } + + strcpy(from_line, buf); + if (fgets(buf, sizeof(buf), mbox_fp) == NULL) { + g_warning(_("malformed mbox: %s\n"), mbox); + fclose(mbox_fp); + return -1; + } + + tmp_file = get_tmp_file(); + + if (filter_junk) { + junk = folder_get_junk(folder); + junk_rule = filter_junk_rule_create(NULL, junk, FALSE); + junk_fltlist.data = junk_rule; + } + + do { + FILE *tmp_fp; + GSList *cur; + gchar *startp, *endp, *rpath; + gint empty_line; + gboolean is_next_msg = FALSE; + gboolean is_junk = FALSE; + FilterInfo *fltinfo; + MsgInfo *msginfo; + + count++; + if (folder->ui_func) + folder->ui_func(folder, dest, folder->ui_func_data ? dest->folder->ui_func_data : GUINT_TO_POINTER(count)); + if (folder_call_ui_func2(folder, dest, count, 0) == FALSE) { + debug_print("Import of mbox cancelled at %u\n", count); + break; + } + + if ((tmp_fp = g_fopen(tmp_file, "wb")) == NULL) { + FILE_OP_ERROR(tmp_file, "fopen"); + g_warning(_("can't open temporary file\n")); + filter_rule_free(junk_rule); + g_free(tmp_file); + fclose(mbox_fp); + return -1; + } + if (change_file_mode_rw(tmp_fp, tmp_file) < 0) + FILE_OP_ERROR(tmp_file, "chmod"); + + /* convert unix From into Return-Path */ + startp = from_line + 5; + endp = strchr(startp, ' '); + if (endp == NULL) + rpath = g_strdup(startp); + else + rpath = g_strndup(startp, endp - startp); + g_strstrip(rpath); + g_snprintf(from_line, sizeof(from_line), + "Return-Path: %s\n", rpath); + g_free(rpath); + + FPUTS_TO_TMP_ABORT_IF_FAIL(from_line); + FPUTS_TO_TMP_ABORT_IF_FAIL(buf); + from_line[0] = '\0'; + + empty_line = 0; + + while (fgets(buf, sizeof(buf), mbox_fp) != NULL) { + if (buf[0] == '\n' || buf[0] == '\r') { + empty_line++; + buf[0] = '\0'; + continue; + } + + /* From separator */ + while (!strncmp(buf, "From ", 5)) { + strcpy(from_line, buf); + if (fgets(buf, sizeof(buf), mbox_fp) == NULL) { + buf[0] = '\0'; + break; + } + + if (is_header_line(buf)) { + is_next_msg = TRUE; + break; + } else if (!strncmp(buf, "From ", 5)) { + continue; + } else if (!strncmp(buf, ">From ", 6)) { + g_memmove(buf, buf + 1, strlen(buf)); + is_next_msg = TRUE; + break; + } else { + g_warning(_("unescaped From found:\n%s"), + from_line); + break; + } + } + if (is_next_msg) break; + + if (empty_line > 0) { + while (empty_line--) + FPUTS_TO_TMP_ABORT_IF_FAIL("\n"); + empty_line = 0; + } + + if (from_line[0] != '\0') { + FPUTS_TO_TMP_ABORT_IF_FAIL(from_line); + from_line[0] = '\0'; + } + + if (buf[0] != '\0') { + if (!strncmp(buf, ">From ", 6)) { + FPUTS_TO_TMP_ABORT_IF_FAIL(buf + 1); + } else + FPUTS_TO_TMP_ABORT_IF_FAIL(buf); + + buf[0] = '\0'; + } + } + + if (empty_line > 0) { + while (--empty_line) + FPUTS_TO_TMP_ABORT_IF_FAIL("\n"); + } + + if (fclose(tmp_fp) == EOF) { + FILE_OP_ERROR(tmp_file, "fclose"); + g_warning(_("can't write to temporary file\n")); + filter_rule_free(junk_rule); + g_unlink(tmp_file); + g_free(tmp_file); + fclose(mbox_fp); + return -1; + } + + fltinfo = filter_info_new(); + fltinfo->flags.perm_flags = MSG_NEW|MSG_UNREAD; + fltinfo->flags.tmp_flags = MSG_RECEIVED; + + msginfo = procheader_parse_file(tmp_file, fltinfo->flags, + FALSE); + if (!msginfo) { + g_warning("proc_mbox_full: procheader_parse_file failed"); + filter_info_free(fltinfo); + filter_rule_free(junk_rule); + g_unlink(tmp_file); + g_free(tmp_file); + fclose(mbox_fp); + return -1; + } + fltinfo->flags = msginfo->flags; + msginfo->file_path = g_strdup(tmp_file); + + if (filter_junk && prefs_common.enable_junk && + prefs_common.filter_junk_before && junk_rule) { + filter_apply_msginfo(&junk_fltlist, msginfo, fltinfo); + if (fltinfo->drop_done) + is_junk = TRUE; + } + + if (!fltinfo->drop_done && apply_filter) + filter_apply_msginfo(prefs_common.fltlist, msginfo, + fltinfo); + + if (!fltinfo->drop_done && + filter_junk && prefs_common.enable_junk && + !prefs_common.filter_junk_before && junk_rule) { + filter_apply_msginfo(&junk_fltlist, msginfo, fltinfo); + if (fltinfo->drop_done) + is_junk = TRUE; + } + + if (fltinfo->actions[FLT_ACTION_MOVE] == FALSE && + fltinfo->actions[FLT_ACTION_DELETE] == FALSE) { + msginfo->flags = fltinfo->flags; + if (folder_item_add_msg_msginfo(dest, msginfo, FALSE) < 0) { + procmsg_msginfo_free(msginfo); + filter_info_free(fltinfo); + filter_rule_free(junk_rule); + g_unlink(tmp_file); + g_free(tmp_file); + fclose(mbox_fp); + return -1; + } + fltinfo->dest_list = g_slist_append(fltinfo->dest_list, + dest); + } + + for (cur = fltinfo->dest_list; cur != NULL; cur = cur->next) { + FolderItem *drop_folder = (FolderItem *)cur->data; + gint val = 0; + + if (folder_table) { + val = GPOINTER_TO_INT(g_hash_table_lookup + (folder_table, + drop_folder)); + } + if (val == 0) { + if (folder_table) { + g_hash_table_insert(folder_table, + drop_folder, + GINT_TO_POINTER(1)); + } + } + } + + if (!is_junk && + fltinfo->actions[FLT_ACTION_DELETE] == FALSE && + fltinfo->actions[FLT_ACTION_MARK_READ] == FALSE) + new_msgs++; + + procmsg_msginfo_free(msginfo); + filter_info_free(fltinfo); + g_unlink(tmp_file); + } while (from_line[0] != '\0'); + + if (junk_rule) + filter_rule_free(junk_rule); + + g_free(tmp_file); + fclose(mbox_fp); + debug_print("%d new messages found.\n", new_msgs); + + return new_msgs; +} + +gint lock_mbox(const gchar *base, LockType type) +{ +#ifdef G_OS_UNIX + gint retval = 0; + + if (type == LOCK_FILE) { +#if HAVE_LIBLOCKFILE + gchar *lockfile; + + lockfile = g_strconcat(base, ".lock", NULL); + if (lockfile_create(lockfile, 0, L_PID) != L_SUCCESS) { + FILE_OP_ERROR(lockfile, "lockfile_create"); + g_free(lockfile); + return -1; + } + g_free(lockfile); +#else + gchar *lockfile, *locklink; + gint retry = 0; + FILE *lockfp; + + lockfile = g_strdup_printf("%s.%d", base, getpid()); + if ((lockfp = g_fopen(lockfile, "wb")) == NULL) { + FILE_OP_ERROR(lockfile, "fopen"); + g_warning(_("can't create lock file %s\n"), lockfile); + g_warning(_("use 'flock' instead of 'file' if possible.\n")); + g_free(lockfile); + return -1; + } + + fprintf(lockfp, "%d\n", getpid()); + fclose(lockfp); + + locklink = g_strconcat(base, ".lock", NULL); + while (link(lockfile, locklink) < 0) { + FILE_OP_ERROR(lockfile, "link"); + if (retry >= 5) { + g_warning(_("can't create %s\n"), lockfile); + g_unlink(lockfile); + g_free(lockfile); + return -1; + } + if (retry == 0) + g_warning(_("mailbox is owned by another" + " process, waiting...\n")); + retry++; + sleep(5); + } + g_unlink(lockfile); + g_free(lockfile); +#endif /* HAVE_LIBLOCKFILE */ + } else if (type == LOCK_FLOCK) { + gint lockfd; + + if ((lockfd = open(base, O_RDWR)) < 0) { + FILE_OP_ERROR(base, "open"); + return -1; + } +#if HAVE_LOCKF + if (lockf(lockfd, F_TLOCK, 0) < 0) { + perror("lockf"); +#elif HAVE_FLOCK + if (flock(lockfd, LOCK_EX|LOCK_NB) < 0) { + perror("flock"); +#else + { +#endif /* HAVE_LOCKF */ + g_warning(_("can't lock %s\n"), base); + if (close(lockfd) < 0) + perror("close"); + return -1; + } + retval = lockfd; + } else { + g_warning(_("invalid lock type\n")); + return -1; + } + + return retval; +#else + return -1; +#endif /* G_OS_UNIX */ +} + +gint unlock_mbox(const gchar *base, gint fd, LockType type) +{ + if (type == LOCK_FILE) { + gchar *lockfile; + + lockfile = g_strconcat(base, ".lock", NULL); +#if HAVE_LIBLOCKFILE + if (lockfile_remove(lockfile) != L_SUCCESS) { + FILE_OP_ERROR(lockfile, "lockfile_remove"); +#else + if (g_unlink(lockfile) < 0) { + FILE_OP_ERROR(lockfile, "unlink"); +#endif /* HAVE_LIBLOCKFILE */ + g_free(lockfile); + return -1; + } + g_free(lockfile); + + return 0; + } else if (type == LOCK_FLOCK) { +#if HAVE_LOCKF + if (lockf(fd, F_ULOCK, 0) < 0) { + perror("lockf"); +#elif HAVE_FLOCK + if (flock(fd, LOCK_UN) < 0) { + perror("flock"); +#else + { +#endif /* HAVE_LOCKF */ + g_warning(_("can't unlock %s\n"), base); + if (close(fd) < 0) + perror("close"); + return -1; + } + + if (close(fd) < 0) { + perror("close"); + return -1; + } + + return 0; + } + + g_warning(_("invalid lock type\n")); + return -1; +} + +gint copy_mbox(const gchar *src, const gchar *dest) +{ + return copy_file(src, dest, TRUE); +} + +void empty_mbox(const gchar *mbox) +{ +#if HAVE_TRUNCATE + if (truncate(mbox, 0) < 0) { +#endif + FILE *fp; + +#if HAVE_TRUNCATE + FILE_OP_ERROR(mbox, "truncate"); +#endif + if ((fp = g_fopen(mbox, "wb")) == NULL) { + FILE_OP_ERROR(mbox, "fopen"); + g_warning(_("can't truncate mailbox to zero.\n")); + return; + } + fclose(fp); +#if HAVE_TRUNCATE + } +#endif +} + +/* read all messages in SRC, and store them into one MBOX file. */ +gint export_to_mbox(FolderItem *src, const gchar *mbox) +{ + GSList *mlist; + gint ret = 0; + + mlist = folder_item_get_msg_list(src, TRUE); + if (mlist) { + ret = export_msgs_to_mbox(src, mlist, mbox); + procmsg_msg_list_free(mlist); + } + + return ret; +} + +/* store MLIST into one MBOX file. */ +gint export_msgs_to_mbox(FolderItem *src, GSList *mlist, const gchar *mbox) +{ + GSList *cur; + MsgInfo *msginfo; + FILE *msg_fp; + FILE *mbox_fp; + gchar buf[BUFFSIZE]; + PrefsAccount *cur_ac; + guint count = 0, length; + time_t date_t_; + + g_return_val_if_fail(src != NULL, -1); + g_return_val_if_fail(src->folder != NULL, -1); + g_return_val_if_fail(mlist != NULL, -1); + g_return_val_if_fail(mbox != NULL, -1); + + debug_print(_("Exporting messages from %s into %s...\n"), + src->path, mbox); + + if ((mbox_fp = g_fopen(mbox, "wb")) == NULL) { + FILE_OP_ERROR(mbox, "fopen"); + return -1; + } + + cur_ac = account_get_current_account(); + + length = g_slist_length(mlist); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + count++; + if (src->folder->ui_func) + src->folder->ui_func(src->folder, src, src->folder->ui_func_data ? src->folder->ui_func_data : GUINT_TO_POINTER(count)); + if (folder_call_ui_func2(src->folder, src, count, length) == FALSE) { + debug_print("Export to mbox cancelled at %u/%u\n", count, length); + break; + } + + msg_fp = procmsg_open_message(msginfo); + if (!msg_fp) + continue; + + strncpy2(buf, + msginfo->from ? msginfo->from : + cur_ac && cur_ac->address ? + cur_ac->address : "unknown", + sizeof(buf)); + extract_address(buf); + + date_t_ = msginfo->date_t; + fprintf(mbox_fp, "From %s %s", buf, ctime(&date_t_)); + + while (fgets(buf, sizeof(buf), msg_fp) != NULL) { + if (!strncmp(buf, "From ", 5)) + fputc('>', mbox_fp); + fputs(buf, mbox_fp); + } + fputc('\n', mbox_fp); + + fclose(msg_fp); + } + + fclose(mbox_fp); + + return 0; +} diff --git a/libsylph/mbox.h b/libsylph/mbox.h new file mode 100644 index 0000000..22300dd --- /dev/null +++ b/libsylph/mbox.h @@ -0,0 +1,56 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2007 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __MBOX_H__ +#define __MBOX_H__ + +#include + +#include "folder.h" + +typedef enum { + LOCK_FILE, + LOCK_FLOCK +} LockType; + +gint proc_mbox (FolderItem *dest, + const gchar *mbox, + GHashTable *folder_table); +gint proc_mbox_full (FolderItem *dest, + const gchar *mbox, + GHashTable *folder_table, + gboolean apply_filter, + gboolean filter_junk); + +gint lock_mbox (const gchar *base, + LockType type); +gint unlock_mbox (const gchar *base, + gint fd, + LockType type); +gint copy_mbox (const gchar *src, + const gchar *dest); +void empty_mbox (const gchar *mbox); + +gint export_to_mbox (FolderItem *src, + const gchar *mbox); +gint export_msgs_to_mbox(FolderItem *src, + GSList *mlist, + const gchar *mbox); + +#endif /* __MBOX_H__ */ diff --git a/libsylph/md5.c b/libsylph/md5.c new file mode 100644 index 0000000..a047471 --- /dev/null +++ b/libsylph/md5.c @@ -0,0 +1,637 @@ +/* + + GNet API added by David Helder 2000-6-11. All + additions and changes placed in the public domain. + + Files originally from: http://www.gxsnmp.org/CVS/gxsnmp/ + + Modified the prefix of functions to prevent conflict with original GNet. + + */ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#include "md5.h" +#include +#include + + +/* ************************************************************ */ +/* Code below is from Colin Plumb implementation */ + + + +struct MD5Context { + guint32 buf[4]; + guint32 bits[2]; + guchar in[64]; + int doByteReverse; +}; + +static void MD5Init(struct MD5Context *context); +static void MD5Update(struct MD5Context *context, guchar const *buf, + guint len); +static void MD5Final(guchar digest[16], struct MD5Context *context); +static void MD5Transform(guint32 buf[4], guint32 const in[16]); + +/* + * This is needed to make RSAREF happy on some MS-DOS compilers. + */ +typedef struct MD5Context MD5_CTX; + + + + +static void byteReverse(guint8 *buf, guint longs); + +/* + * Note: this code is harmless on little-endian machines. + */ +void +byteReverse(guint8 *buf, guint longs) +{ + guint32 t; + do + { + t = (guint32) ((guint) buf[3] << 8 | buf[2]) << 16 | + ((guint) buf[1] << 8 | buf[0]); + *(guint32 *) buf = t; + buf += 4; + } + while (--longs); +} + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; + +#if (G_BYTE_ORDER == G_BIG_ENDIAN) + ctx->doByteReverse = 1; +#else + ctx->doByteReverse = 0; +#endif +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(struct MD5Context *ctx, guint8 const *buf, guint len) +{ + guint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((guint32) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) + { + guint8 *p = (guint8 *) ctx->in + t; + + t = 64 - t; + if (len < t) + { + g_memmove(p, buf, len); + return; + } + g_memmove(p, buf, t); + if (ctx->doByteReverse) + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (guint32 *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) + { + g_memmove(ctx->in, buf, 64); + if (ctx->doByteReverse) + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (guint32 *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + g_memmove(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(guint8 digest[16], struct MD5Context *ctx) +{ + guint count; + guint8 *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) + { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + if (ctx->doByteReverse) + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (guint32 *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } + else + { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + if (ctx->doByteReverse) + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((guint32 *) ctx->in)[14] = ctx->bits[0]; + ((guint32 *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, (guint32 *) ctx->in); + if (ctx->doByteReverse) + byteReverse((guint8 *) ctx->buf, 4); + g_memmove(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(guint32 buf[4], guint32 const in[16]) +{ + register guint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + + + +/* ************************************************************ */ +/* Code below is David Helder's API for GNet */ + +struct _SMD5 +{ + struct MD5Context ctx; + gchar digest[S_GNET_MD5_HASH_LENGTH]; +}; + + +/** + * s_gnet_md5_new: + * @buffer: buffer to hash + * @length: length of @buffer + * + * Creates a #SMD5 from @buffer. + * + * Returns: a new #SMD5. + * + **/ +SMD5* +s_gnet_md5_new (const guchar* buffer, guint length) +{ + SMD5* md5; + + md5 = g_new0 (SMD5, 1); + MD5Init (&md5->ctx); + MD5Update (&md5->ctx, buffer, length); + MD5Final ((gpointer) &md5->digest, &md5->ctx); + + return md5; +} + + + +/** + * s_gnet_md5_new_string: + * @str: hexidecimal string + * + * Creates a #SMD5 from @str. @str is a hexidecimal string + * representing the digest. + * + * Returns: a new #SMD5. + * + **/ +SMD5* +s_gnet_md5_new_string (const gchar* str) +{ + SMD5* md5; + guint i; + + g_return_val_if_fail (str, NULL); + g_return_val_if_fail (strlen(str) >= (S_GNET_MD5_HASH_LENGTH * 2), NULL); + + md5 = g_new0 (SMD5, 1); + + for (i = 0; i < (S_GNET_MD5_HASH_LENGTH * 2); ++i) + { + guint val = 0; + + switch (str[i]) + { + case '0': val = 0; break; + case '1': val = 1; break; + case '2': val = 2; break; + case '3': val = 3; break; + case '4': val = 4; break; + case '5': val = 5; break; + case '6': val = 6; break; + case '7': val = 7; break; + case '8': val = 8; break; + case '9': val = 9; break; + case 'A': + case 'a': val = 10; break; + case 'B': + case 'b': val = 11; break; + case 'C': + case 'c': val = 12; break; + case 'D': + case 'd': val = 13; break; + case 'E': + case 'e': val = 14; break; + case 'F': + case 'f': val = 15; break; + default: + g_return_val_if_fail (FALSE, NULL); + } + + if (i % 2) + md5->digest[i / 2] |= val; + else + md5->digest[i / 2] = val << 4; + } + + return md5; +} + + + +/** + * s_gnet_md5_clone + * @md5: a #SMD5 + * + * Copies a #SMD5. + * + * Returns: a copy of @md5. + * + **/ +SMD5* +s_gnet_md5_clone (const SMD5* md5) +{ + SMD5* md52; + + g_return_val_if_fail (md5, NULL); + + md52 = g_new0 (SMD5, 1); + md52->ctx = md5->ctx; + memcpy (md52->digest, md5->digest, sizeof(md5->digest)); + + return md52; +} + + + +/** + * s_gnet_md5_delete + * @md5: a #SMD5 + * + * Deletes a #SMD5. + * + **/ +void +s_gnet_md5_delete (SMD5* md5) +{ + if (md5) + g_free (md5); +} + + + +/** + * s_gnet_md5_new_incremental + * + * Creates a #SMD5 incrementally. After creating a #SMD5, call + * s_gnet_md5_update() one or more times to hash data. Finally, call + * s_gnet_md5_final() to compute the final hash value. + * + * Returns: a new #SMD5. + * + **/ +SMD5* +s_gnet_md5_new_incremental (void) +{ + SMD5* md5; + + md5 = g_new0 (SMD5, 1); + MD5Init (&md5->ctx); + return md5; +} + + +/** + * s_gnet_md5_update + * @md5: a #SMD5 + * @buffer: buffer to add + * @length: length of @buffer + * + * Updates the hash with @buffer. This may be called several times + * on a hash created by s_gnet_md5_new_incremental() before being + * finalized by calling s_gnet_md5_final(). + * + **/ +void +s_gnet_md5_update (SMD5* md5, const guchar* buffer, guint length) +{ + g_return_if_fail (md5); + + MD5Update (&md5->ctx, buffer, length); +} + + +/** + * s_gnet_md5_final + * @md5: a #SMD5 + * + * Calcuates the final hash value of a #SMD5. This should only be + * called on an #SMD5 created by s_gnet_md5_new_incremental(). + * + **/ +void +s_gnet_md5_final (SMD5* md5) +{ + g_return_if_fail (md5); + + MD5Final ((gpointer) &md5->digest, &md5->ctx); +} + + +/* **************************************** */ + +/** + * s_gnet_md5_equal + * @p1: first #SMD5. + * @p2: second #SMD5. + * + * Compares two #SMD5's for equality. + * + * Returns: TRUE if they are equal; FALSE otherwise. + * + **/ +gint +s_gnet_md5_equal (gconstpointer p1, gconstpointer p2) +{ + SMD5* md5a = (SMD5*) p1; + SMD5* md5b = (SMD5*) p2; + guint i; + + for (i = 0; i < S_GNET_MD5_HASH_LENGTH; ++i) + if (md5a->digest[i] != md5b->digest[i]) + return FALSE; + + return TRUE; +} + + +/** + * s_gnet_md5_hash + * @p: a #SMD5 + * + * Creates a hash code for a #SMD5 for use with GHashTable. This + * hash value is not the same as the MD5 digest. + * + * Returns: the hash code for @p. + * + **/ +guint +s_gnet_md5_hash (gconstpointer p) +{ + const SMD5* md5 = (const SMD5*) p; + const guint* q; + + g_return_val_if_fail (md5, 0); + + q = (const guint*) md5->digest; + + return (q[0] ^ q[1] ^ q[2] ^ q[3]); +} + + +/** + * s_gnet_md5_get_digest + * @md5: a #SMD5 + * + * Gets the raw MD5 digest. + * + * Returns: a callee-owned buffer containing the MD5 hash digest. + * The buffer is %S_GNET_MD5_HASH_LENGTH bytes long. + * + **/ +gchar* +s_gnet_md5_get_digest (const SMD5* md5) +{ + g_return_val_if_fail (md5, NULL); + + return (gchar*) md5->digest; +} + + +static gchar bits2hex[16] = { '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f' }; + +/** + * s_gnet_md5_get_string + * @md5: a #SMD5 + * + * Gets the digest represented a human-readable string. + * + * Returns: a hexadecimal string representing the digest. The string + * is 2 * %S_GNET_MD5_HASH_LENGTH bytes long and NULL terminated. The + * string is caller owned. + * + **/ +gchar* +s_gnet_md5_get_string (const SMD5* md5) +{ + gchar* str; + guint i; + + g_return_val_if_fail (md5, NULL); + + str = g_new (gchar, S_GNET_MD5_HASH_LENGTH * 2 + 1); + str[S_GNET_MD5_HASH_LENGTH * 2] = '\0'; + + for (i = 0; i < S_GNET_MD5_HASH_LENGTH; ++i) + { + str[i * 2] = bits2hex[(md5->digest[i] & 0xF0) >> 4]; + str[(i * 2) + 1] = bits2hex[(md5->digest[i] & 0x0F) ]; + } + + return str; +} + + + +/** + * s_gnet_md5_copy_string + * @md5: a #SMD5 + * @buffer: buffer at least 2 * %S_GNET_MD5_HASH_LENGTH bytes long + * + * Copies the digest, represented as a string, into @buffer. The + * string is not NULL terminated. + * + **/ +void +s_gnet_md5_copy_string (const SMD5* md5, gchar* buffer) +{ + guint i; + + g_return_if_fail (md5); + g_return_if_fail (buffer); + + for (i = 0; i < S_GNET_MD5_HASH_LENGTH; ++i) + { + buffer[i * 2] = bits2hex[(md5->digest[i] & 0xF0) >> 4]; + buffer[(i * 2) + 1] = bits2hex[(md5->digest[i] & 0x0F) ]; + } +} diff --git a/libsylph/md5.h b/libsylph/md5.h new file mode 100644 index 0000000..e50b387 --- /dev/null +++ b/libsylph/md5.h @@ -0,0 +1,61 @@ +/** + + This code is in the public domain. See md5.c for details. + + Authors: + Colin Plumb [original author] + David Helder [GNet API] + + Modified the prefix of functions to prevent conflict with original GNet. + + */ + + +#ifndef S_GNET_MD5_H +#define S_GNET_MD5_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * SMD5 + * + * SMD5 is a MD5 hash. + * + **/ +typedef struct _SMD5 SMD5; + +/** + * S_GNET_MD5_HASH_LENGTH + * + * Length of the MD5 hash in bytes. + **/ +#define S_GNET_MD5_HASH_LENGTH 16 + + +SMD5* s_gnet_md5_new (const guchar* buffer, guint length); +SMD5* s_gnet_md5_new_string (const gchar* str); +SMD5* s_gnet_md5_clone (const SMD5* md5); +void s_gnet_md5_delete (SMD5* md5); + +SMD5* s_gnet_md5_new_incremental (void); +void s_gnet_md5_update (SMD5* md5, const guchar* buffer, guint length); +void s_gnet_md5_final (SMD5* md5); + +gboolean s_gnet_md5_equal (gconstpointer p1, gconstpointer p2); +guint s_gnet_md5_hash (gconstpointer p); + +gchar* s_gnet_md5_get_digest (const SMD5* md5); +gchar* s_gnet_md5_get_string (const SMD5* md5); + +void s_gnet_md5_copy_string (const SMD5* md5, gchar* buffer); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* S_GNET_MD5_H */ diff --git a/libsylph/md5_hmac.c b/libsylph/md5_hmac.c new file mode 100644 index 0000000..77ae39e --- /dev/null +++ b/libsylph/md5_hmac.c @@ -0,0 +1,134 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2006 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "md5.h" +#include "md5_hmac.h" + +/* +** Function: md5_hmac_get +** taken from the file rfc2104.txt +** originally written by Martin Schaaf +** rewritten by Hiroyuki Yamamoto +*/ +static SMD5* +md5_hmac_get(const guchar *text, gint text_len, + const guchar *key, gint key_len) +{ + SMD5 *md5; + guchar k_ipad[64]; /* inner padding - + * key XORd with ipad + */ + guchar k_opad[64]; /* outer padding - + * key XORd with opad + */ + guchar digest[S_GNET_MD5_HASH_LENGTH]; + gint i; + + /* start out by storing key in pads */ + memset(k_ipad, 0, sizeof k_ipad); + memset(k_opad, 0, sizeof k_opad); + + if (key_len > 64) { + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + SMD5 *tmd5; + + tmd5 = s_gnet_md5_new(key, key_len); + memcpy(k_ipad, s_gnet_md5_get_digest(tmd5), + S_GNET_MD5_HASH_LENGTH); + memcpy(k_opad, s_gnet_md5_get_digest(tmd5), + S_GNET_MD5_HASH_LENGTH); + s_gnet_md5_delete(tmd5); + } else { + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + /* + * perform inner MD5 + */ + md5 = s_gnet_md5_new_incremental(); /* init context for 1st + * pass */ + s_gnet_md5_update(md5, k_ipad, 64); /* start with inner pad */ + s_gnet_md5_update(md5, text, text_len); /* then text of datagram */ + s_gnet_md5_final(md5); /* finish up 1st pass */ + memcpy(digest, s_gnet_md5_get_digest(md5), S_GNET_MD5_HASH_LENGTH); + s_gnet_md5_delete(md5); + + /* + * perform outer MD5 + */ + md5 = s_gnet_md5_new_incremental(); /* init context for 2nd + * pass */ + s_gnet_md5_update(md5, k_opad, 64); /* start with outer pad */ + s_gnet_md5_update(md5, digest, 16); /* then results of 1st + * hash */ + s_gnet_md5_final(md5); /* finish up 2nd pass */ + + return md5; +} + +void +md5_hmac(guchar *digest, + const guchar *text, gint text_len, + const guchar *key, gint key_len) +{ + SMD5 *md5; + + md5 = md5_hmac_get(text, text_len, key, key_len); + memcpy(digest, s_gnet_md5_get_digest(md5), S_GNET_MD5_HASH_LENGTH); + s_gnet_md5_delete(md5); +} + +void +md5_hex_hmac(gchar *hexdigest, + const guchar *text, gint text_len, + const guchar *key, gint key_len) +{ + SMD5 *md5; + + md5 = md5_hmac_get(text, text_len, key, key_len); + s_gnet_md5_copy_string(md5, hexdigest); + hexdigest[S_GNET_MD5_HASH_LENGTH * 2] = '\0'; + s_gnet_md5_delete(md5); +} diff --git a/libsylph/md5_hmac.h b/libsylph/md5_hmac.h new file mode 100644 index 0000000..14c9318 --- /dev/null +++ b/libsylph/md5_hmac.h @@ -0,0 +1,33 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __MD5_HMAC_H__ +#define __MD5_HMAC_H__ + +#include + +void md5_hmac (guchar *digest, + const guchar* text, gint text_len, + const guchar* key, gint key_len); +void md5_hex_hmac (gchar *hexdigest, + const guchar* text, gint text_len, + const guchar* key, gint key_len); + +#endif /* __MD5_HMAC_H__ */ + diff --git a/libsylph/mh.c b/libsylph/mh.c new file mode 100644 index 0000000..c1f0679 --- /dev/null +++ b/libsylph/mh.c @@ -0,0 +1,1898 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2009 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef G_OS_WIN32 +# include +#endif + +#undef MEASURE_TIME + +#include "sylmain.h" +#include "folder.h" +#include "mh.h" +#include "procmsg.h" +#include "procheader.h" +#include "utils.h" +#include "prefs_common.h" + +#if USE_THREADS +G_LOCK_DEFINE_STATIC(mh); +#define S_LOCK(name) G_LOCK(name) +#define S_UNLOCK(name) G_UNLOCK(name) +#else +#define S_LOCK(name) +#define S_UNLOCK(name) +#endif + +static void mh_folder_init (Folder *folder, + const gchar *name, + const gchar *path); + +static Folder *mh_folder_new (const gchar *name, + const gchar *path); +static void mh_folder_destroy (Folder *folder); + +static GSList *mh_get_msg_list (Folder *folder, + FolderItem *item, + gboolean use_cache); +static GSList *mh_get_uncached_msg_list(Folder *folder, + FolderItem *item); +static gchar *mh_fetch_msg (Folder *folder, + FolderItem *item, + gint num); +static MsgInfo *mh_get_msginfo (Folder *folder, + FolderItem *item, + gint num); +static gint mh_add_msg (Folder *folder, + FolderItem *dest, + const gchar *file, + MsgFlags *flags, + gboolean remove_source); +static gint mh_add_msgs (Folder *folder, + FolderItem *dest, + GSList *file_list, + gboolean remove_source, + gint *first); +static gint mh_add_msg_msginfo (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo, + gboolean remove_source); +static gint mh_add_msgs_msginfo (Folder *folder, + FolderItem *dest, + GSList *msglist, + gboolean remove_source, + gint *first); +static gint mh_move_msg (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo); +static gint mh_move_msgs (Folder *folder, + FolderItem *dest, + GSList *msglist); +static gint mh_copy_msg (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo); +static gint mh_copy_msgs (Folder *folder, + FolderItem *dest, + GSList *msglist); +static gint mh_remove_msg (Folder *folder, + FolderItem *item, + MsgInfo *msginfo); +static gint mh_remove_all_msg (Folder *folder, + FolderItem *item); +static gboolean mh_is_msg_changed (Folder *folder, + FolderItem *item, + MsgInfo *msginfo); +static gint mh_close (Folder *folder, + FolderItem *item); + +static gint mh_scan_folder_full (Folder *folder, + FolderItem *item, + gboolean count_sum); +static gint mh_scan_folder (Folder *folder, + FolderItem *item); +static gint mh_scan_tree (Folder *folder); + +static gint mh_create_tree (Folder *folder); +static FolderItem *mh_create_folder (Folder *folder, + FolderItem *parent, + const gchar *name); +static gint mh_rename_folder (Folder *folder, + FolderItem *item, + const gchar *name); +static gint mh_move_folder (Folder *folder, + FolderItem *item, + FolderItem *new_parent); +static gint mh_remove_folder (Folder *folder, + FolderItem *item); + +static gchar *mh_get_new_msg_filename (FolderItem *dest); + +static gint mh_do_move_msgs (Folder *folder, + FolderItem *dest, + GSList *msglist); + +static time_t mh_get_mtime (FolderItem *item); +static GSList *mh_get_uncached_msgs (GHashTable *msg_table, + FolderItem *item); +static MsgInfo *mh_parse_msg (const gchar *file, + FolderItem *item); +static void mh_remove_missing_folder_items (Folder *folder); +static void mh_scan_tree_recursive (FolderItem *item); + +static gboolean mh_rename_folder_func (GNode *node, + gpointer data); + +static FolderClass mh_class = +{ + F_MH, + + mh_folder_new, + mh_folder_destroy, + + mh_scan_tree, + mh_create_tree, + + mh_get_msg_list, + mh_get_uncached_msg_list, + mh_fetch_msg, + mh_get_msginfo, + mh_add_msg, + mh_add_msgs, + mh_add_msg_msginfo, + mh_add_msgs_msginfo, + mh_move_msg, + mh_move_msgs, + mh_copy_msg, + mh_copy_msgs, + mh_remove_msg, + NULL, + mh_remove_all_msg, + mh_is_msg_changed, + mh_close, + mh_scan_folder, + + mh_create_folder, + mh_rename_folder, + mh_move_folder, + mh_remove_folder, +}; + + +FolderClass *mh_get_class(void) +{ + return &mh_class; +} + +static Folder *mh_folder_new(const gchar *name, const gchar *path) +{ + Folder *folder; + + folder = (Folder *)g_new0(MHFolder, 1); + mh_folder_init(folder, name, path); + + return folder; +} + +static void mh_folder_destroy(Folder *folder) +{ + folder_local_folder_destroy(LOCAL_FOLDER(folder)); +} + +static void mh_folder_init(Folder *folder, const gchar *name, const gchar *path) +{ + folder->klass = mh_get_class(); + folder_local_folder_init(folder, name, path); +} + +static GSList *mh_get_msg_list_full(Folder *folder, FolderItem *item, + gboolean use_cache, gboolean uncached_only) +{ + GSList *mlist; + GHashTable *msg_table; + time_t cur_mtime; + GSList *newlist = NULL; +#ifdef MEASURE_TIME + GTimer *timer; +#endif + + g_return_val_if_fail(item != NULL, NULL); + + S_LOCK(mh); + +#ifdef MEASURE_TIME + timer = g_timer_new(); +#endif + + cur_mtime = mh_get_mtime(item); + + if (use_cache && item->mtime == cur_mtime) { + debug_print("Folder is not modified.\n"); + mlist = procmsg_read_cache(item, FALSE); + if (!mlist) { + mlist = mh_get_uncached_msgs(NULL, item); + if (mlist) + item->cache_dirty = TRUE; + } + } else if (use_cache) { + GSList *cur, *next; + gboolean strict_cache_check = prefs_common.strict_cache_check; + + if (item->stype == F_QUEUE || item->stype == F_DRAFT) + strict_cache_check = TRUE; + + mlist = procmsg_read_cache(item, strict_cache_check); + msg_table = procmsg_msg_hash_table_create(mlist); + newlist = mh_get_uncached_msgs(msg_table, item); + if (newlist) + item->cache_dirty = TRUE; + if (msg_table) + g_hash_table_destroy(msg_table); + + if (!strict_cache_check) { + /* remove nonexistent messages */ + for (cur = mlist; cur != NULL; cur = next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + next = cur->next; + if (!MSG_IS_CACHED(msginfo->flags)) { + debug_print("removing nonexistent message %d from cache\n", msginfo->msgnum); + mlist = g_slist_remove(mlist, msginfo); + procmsg_msginfo_free(msginfo); + item->cache_dirty = TRUE; + item->mark_dirty = TRUE; + } + } + } + + mlist = g_slist_concat(mlist, newlist); + } else { + mlist = mh_get_uncached_msgs(NULL, item); + item->cache_dirty = TRUE; + newlist = mlist; + } + + procmsg_set_flags(mlist, item); + + if (!uncached_only) + mlist = procmsg_sort_msg_list(mlist, item->sort_key, + item->sort_type); + + if (item->mark_queue) + item->mark_dirty = TRUE; + +#ifdef MEASURE_TIME + g_timer_stop(timer); + g_print("%s: %s: elapsed time: %f sec\n", + G_STRFUNC, item->path, g_timer_elapsed(timer, NULL)); + g_timer_destroy(timer); +#endif + debug_print("cache_dirty: %d, mark_dirty: %d\n", + item->cache_dirty, item->mark_dirty); + + if (!item->opened) { + item->mtime = cur_mtime; + if (item->cache_dirty) + procmsg_write_cache_list(item, mlist); + if (item->mark_dirty) + procmsg_write_flags_list(item, mlist); + } + + if (uncached_only) { + GSList *cur; + + if (newlist == NULL) { + procmsg_msg_list_free(mlist); + S_UNLOCK(mh); + return NULL; + } + if (mlist == newlist) { + S_UNLOCK(mh); + return newlist; + } + for (cur = mlist; cur != NULL; cur = cur->next) { + if (cur->next == newlist) { + cur->next = NULL; + procmsg_msg_list_free(mlist); + S_UNLOCK(mh); + return newlist; + } + } + procmsg_msg_list_free(mlist); + S_UNLOCK(mh); + return NULL; + } + + S_UNLOCK(mh); + return mlist; +} + +static GSList *mh_get_msg_list(Folder *folder, FolderItem *item, + gboolean use_cache) +{ + return mh_get_msg_list_full(folder, item, use_cache, FALSE); +} + +static GSList *mh_get_uncached_msg_list(Folder *folder, FolderItem *item) +{ + return mh_get_msg_list_full(folder, item, TRUE, TRUE); +} + +static gchar *mh_fetch_msg(Folder *folder, FolderItem *item, gint num) +{ + gchar *path; + gchar *file; + gchar buf[16]; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(num > 0, NULL); + + if (item->last_num < 0 || num > item->last_num) { + mh_scan_folder(folder, item); + if (item->last_num < 0) return NULL; + } + + if (num > item->last_num) + return NULL; + + path = folder_item_get_path(item); + file = g_strconcat(path, G_DIR_SEPARATOR_S, utos_buf(buf, num), NULL); + g_free(path); + if (!is_file_exist(file)) { + g_free(file); + return NULL; + } + + return file; +} + +static MsgInfo *mh_get_msginfo(Folder *folder, FolderItem *item, gint num) +{ + MsgInfo *msginfo; + gchar *file; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(num > 0, NULL); + + file = mh_fetch_msg(folder, item, num); + if (!file) return NULL; + + msginfo = mh_parse_msg(file, item); + if (msginfo) + msginfo->msgnum = num; + + g_free(file); + + return msginfo; +} + +static gchar *mh_get_new_msg_filename(FolderItem *dest) +{ + gchar *destfile; + gchar *destpath; + + destpath = folder_item_get_path(dest); + g_return_val_if_fail(destpath != NULL, NULL); + + if (!is_dir_exist(destpath)) + make_dir_hier(destpath); + + for (;;) { + destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR, + dest->last_num + 1); + if (is_file_entry_exist(destfile)) { + dest->last_num++; + g_free(destfile); + } else + break; + } + + g_free(destpath); + + return destfile; +} + +#define SET_DEST_MSG_FLAGS(fp, dest, n, fl) \ +{ \ + MsgInfo newmsginfo; \ + \ + newmsginfo.msgnum = n; \ + newmsginfo.flags = fl; \ + if (dest->stype == F_OUTBOX || \ + dest->stype == F_QUEUE || \ + dest->stype == F_DRAFT) { \ + MSG_UNSET_PERM_FLAGS(newmsginfo.flags, \ + MSG_NEW|MSG_UNREAD|MSG_DELETED); \ + } else if (dest->stype == F_TRASH) { \ + MSG_UNSET_PERM_FLAGS(newmsginfo.flags, MSG_DELETED); \ + } \ + \ + if (fp) \ + procmsg_write_flags(&newmsginfo, fp); \ + else \ + procmsg_add_mark_queue(dest, n, newmsginfo.flags); \ +} + +static gint mh_add_msg(Folder *folder, FolderItem *dest, const gchar *file, + MsgFlags *flags, gboolean remove_source) +{ + GSList file_list; + MsgFileInfo fileinfo; + + g_return_val_if_fail(file != NULL, -1); + + fileinfo.file = (gchar *)file; + fileinfo.flags = flags; + file_list.data = &fileinfo; + file_list.next = NULL; + + return mh_add_msgs(folder, dest, &file_list, remove_source, NULL); +} + +static gint mh_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list, + gboolean remove_source, gint *first) +{ + gchar *destfile; + GSList *cur; + MsgFileInfo *fileinfo; + MsgInfo *msginfo; + gint first_ = 0; + FILE *fp = NULL; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(file_list != NULL, -1); + + if (dest->last_num < 0) { + mh_scan_folder(folder, dest); + if (dest->last_num < 0) return -1; + } + + S_LOCK(mh); + + if (!dest->opened) { + if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL) + g_warning("mh_add_msgs: can't open mark file."); + } + + for (cur = file_list; cur != NULL; cur = cur->next) { + MsgFlags flags = {MSG_NEW|MSG_UNREAD, 0}; + + fileinfo = (MsgFileInfo *)cur->data; + if (fileinfo->flags) + flags = *fileinfo->flags; + msginfo = procheader_parse_file(fileinfo->file, flags, 0); + if (!msginfo) { + if (fp) fclose(fp); + S_UNLOCK(mh); + return -1; + } + + destfile = mh_get_new_msg_filename(dest); + if (destfile == NULL) { + S_UNLOCK(mh); + return -1; + } + if (first_ == 0 || first_ > dest->last_num + 1) + first_ = dest->last_num + 1; + + if (syl_link(fileinfo->file, destfile) < 0) { + if (copy_file(fileinfo->file, destfile, TRUE) < 0) { + g_warning(_("can't copy message %s to %s\n"), + fileinfo->file, destfile); + g_free(destfile); + if (fp) fclose(fp); + S_UNLOCK(mh); + return -1; + } + } + + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "add-msg", dest, destfile, dest->last_num + 1); + + g_free(destfile); + dest->last_num++; + dest->total++; + dest->updated = TRUE; + dest->mtime = 0; + + if (MSG_IS_RECEIVED(flags)) { + /* resets new flags of existing messages on + received mode */ + if (dest->unmarked_num == 0) + dest->new = 0; + dest->unmarked_num++; + procmsg_add_mark_queue(dest, dest->last_num, flags); + } else { + SET_DEST_MSG_FLAGS(fp, dest, dest->last_num, flags); + } + procmsg_add_cache_queue(dest, dest->last_num, msginfo); + if (MSG_IS_NEW(flags)) + dest->new++; + if (MSG_IS_UNREAD(flags)) + dest->unread++; + } + + if (fp) + fclose(fp); + + if (first) + *first = first_; + + if (remove_source) { + for (cur = file_list; cur != NULL; cur = cur->next) { + fileinfo = (MsgFileInfo *)cur->data; + if (g_unlink(fileinfo->file) < 0) + FILE_OP_ERROR(fileinfo->file, "unlink"); + } + } + + S_UNLOCK(mh); + return dest->last_num; +} + + +static gint mh_add_msg_msginfo(Folder *folder, FolderItem *dest, + MsgInfo *msginfo, gboolean remove_source) +{ + GSList msglist; + + g_return_val_if_fail(msginfo != NULL, -1); + + msglist.data = msginfo; + msglist.next = NULL; + + return mh_add_msgs_msginfo(folder, dest, &msglist, remove_source, NULL); +} + +static gint mh_add_msgs_msginfo(Folder *folder, FolderItem *dest, + GSList *msglist, gboolean remove_source, + gint *first) +{ + GSList *cur; + MsgInfo *msginfo; + gchar *srcfile; + gchar *destfile; + gint first_ = 0; + FILE *fp = NULL; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + + if (dest->last_num < 0) { + mh_scan_folder(folder, dest); + if (dest->last_num < 0) return -1; + } + + S_LOCK(mh); + + if (!dest->opened) { + if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL) + g_warning("mh_add_msgs_msginfo: can't open mark file."); + } + + for (cur = msglist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + destfile = mh_get_new_msg_filename(dest); + if (!destfile) { + if (fp) fclose(fp); + S_UNLOCK(mh); + return -1; + } + if (first_ == 0 || first_ > dest->last_num + 1) + first_ = dest->last_num + 1; + + srcfile = procmsg_get_message_file(msginfo); + if (!srcfile) { + if (fp) fclose(fp); + g_free(destfile); + S_UNLOCK(mh); + return -1; + } + if (syl_link(srcfile, destfile) < 0) { + if (copy_file(srcfile, destfile, TRUE) < 0) { + g_warning("mh_add_msgs_msginfo: can't copy message %s to %s", srcfile, destfile); + g_free(srcfile); + g_free(destfile); + if (fp) fclose(fp); + S_UNLOCK(mh); + return -1; + } + } + + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "add-msg", dest, destfile, dest->last_num + 1); + + g_free(srcfile); + g_free(destfile); + dest->last_num++; + dest->total++; + dest->updated = TRUE; + dest->mtime = 0; + + if (MSG_IS_RECEIVED(msginfo->flags)) { + /* resets new flags of existing messages on + received mode */ + if (dest->unmarked_num == 0) + dest->new = 0; + dest->unmarked_num++; + procmsg_add_mark_queue(dest, dest->last_num, + msginfo->flags); + } else { + SET_DEST_MSG_FLAGS(fp, dest, dest->last_num, + msginfo->flags); + } + procmsg_add_cache_queue(dest, dest->last_num, msginfo); + if (MSG_IS_NEW(msginfo->flags)) + dest->new++; + if (MSG_IS_UNREAD(msginfo->flags)) + dest->unread++; + } + + if (fp) + fclose(fp); + + if (first) + *first = first_; + + if (remove_source) { + for (cur = msglist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + srcfile = procmsg_get_message_file(msginfo); + if (g_unlink(srcfile) < 0) + FILE_OP_ERROR(srcfile, "unlink"); + g_free(srcfile); + } + } + + S_UNLOCK(mh); + return dest->last_num; +} + + +static gint mh_do_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist) +{ + FolderItem *src; + gchar *srcfile; + gchar *destfile; + GSList *cur; + MsgInfo *msginfo; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + + if (dest->last_num < 0) { + mh_scan_folder(folder, dest); + if (dest->last_num < 0) return -1; + } + + S_LOCK(mh); + + for (cur = msglist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + src = msginfo->folder; + + if (src == dest) { + g_warning(_("the src folder is identical to the dest.\n")); + continue; + } + debug_print("Moving message %s/%d to %s ...\n", + src->path, msginfo->msgnum, dest->path); + + destfile = mh_get_new_msg_filename(dest); + if (!destfile) break; + srcfile = procmsg_get_message_file(msginfo); + + /* g_signal_emit_by_name(syl_app_get(), "remove-msg", src, srcfile, msginfo->msgnum); */ + + if (move_file(srcfile, destfile, FALSE) < 0) { + g_free(srcfile); + g_free(destfile); + break; + } + + if (syl_app_get()) { + g_signal_emit_by_name(syl_app_get(), "add-msg", dest, destfile, dest->last_num + 1); + g_signal_emit_by_name(syl_app_get(), "remove-msg", src, srcfile, msginfo->msgnum); + } + + g_free(srcfile); + g_free(destfile); + src->total--; + src->updated = TRUE; + src->mtime = 0; + dest->last_num++; + dest->total++; + dest->updated = TRUE; + dest->mtime = 0; + + SET_DEST_MSG_FLAGS(NULL, dest, dest->last_num, msginfo->flags); + procmsg_add_cache_queue(dest, dest->last_num, msginfo); + + if (MSG_IS_NEW(msginfo->flags)) { + src->new--; + dest->new++; + } + if (MSG_IS_UNREAD(msginfo->flags)) { + src->unread--; + dest->unread++; + } + + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID); + } + + if (!dest->opened) { + procmsg_flush_mark_queue(dest, NULL); + procmsg_flush_cache_queue(dest, NULL); + } + + S_UNLOCK(mh); + return dest->last_num; +} + +static gint mh_move_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo) +{ + GSList msglist; + + g_return_val_if_fail(msginfo != NULL, -1); + + msglist.data = msginfo; + msglist.next = NULL; + + return mh_move_msgs(folder, dest, &msglist); +} + +static gint mh_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist) +{ + MsgInfo *msginfo; + gint ret = 0; + gint first; + + msginfo = (MsgInfo *)msglist->data; + if (folder == msginfo->folder->folder) + return mh_do_move_msgs(folder, dest, msglist); + + ret = mh_add_msgs_msginfo(folder, dest, msglist, FALSE, &first); + + if (ret != -1) + ret = folder_item_remove_msgs(msginfo->folder, msglist); + + return ret; +} + +static gint mh_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 mh_copy_msgs(folder, dest, &msglist); +} + +static gint mh_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist) +{ + gchar *srcfile; + gchar *destfile; + GSList *cur; + MsgInfo *msginfo; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + + if (dest->last_num < 0) { + mh_scan_folder(folder, dest); + if (dest->last_num < 0) return -1; + } + + S_LOCK(mh); + + for (cur = msglist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + if (msginfo->folder == dest) { + g_warning(_("the src folder is identical to the dest.\n")); + continue; + } + debug_print(_("Copying message %s/%d to %s ...\n"), + msginfo->folder->path, msginfo->msgnum, dest->path); + + destfile = mh_get_new_msg_filename(dest); + if (!destfile) break; + srcfile = procmsg_get_message_file(msginfo); + + if (copy_file(srcfile, destfile, TRUE) < 0) { + FILE_OP_ERROR(srcfile, "copy"); + g_free(srcfile); + g_free(destfile); + break; + } + + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "add-msg", dest, destfile, dest->last_num + 1); + + g_free(srcfile); + g_free(destfile); + dest->last_num++; + dest->total++; + dest->updated = TRUE; + dest->mtime = 0; + + SET_DEST_MSG_FLAGS(NULL, dest, dest->last_num, msginfo->flags); + procmsg_add_cache_queue(dest, dest->last_num, msginfo); + + if (MSG_IS_NEW(msginfo->flags)) + dest->new++; + if (MSG_IS_UNREAD(msginfo->flags)) + dest->unread++; + } + + if (!dest->opened) { + procmsg_flush_mark_queue(dest, NULL); + procmsg_flush_cache_queue(dest, NULL); + } + + S_UNLOCK(mh); + return dest->last_num; +} + +static gint mh_remove_msg(Folder *folder, FolderItem *item, MsgInfo *msginfo) +{ + gchar *file; + + g_return_val_if_fail(item != NULL, -1); + + file = mh_fetch_msg(folder, item, msginfo->msgnum); + g_return_val_if_fail(file != NULL, -1); + + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "remove-msg", item, file, msginfo->msgnum); + + S_LOCK(mh); + + if (g_unlink(file) < 0) { + FILE_OP_ERROR(file, "unlink"); + g_free(file); + S_UNLOCK(mh); + return -1; + } + g_free(file); + + item->total--; + item->updated = TRUE; + item->mtime = 0; + if (MSG_IS_NEW(msginfo->flags)) + item->new--; + if (MSG_IS_UNREAD(msginfo->flags)) + item->unread--; + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID); + + S_UNLOCK(mh); + + if (msginfo->msgnum == item->last_num) + mh_scan_folder_full(folder, item, FALSE); + + return 0; +} + +static gint mh_remove_all_msg(Folder *folder, FolderItem *item) +{ + gchar *path; + gint val; + + g_return_val_if_fail(item != NULL, -1); + + path = folder_item_get_path(item); + g_return_val_if_fail(path != NULL, -1); + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "remove-all-msg", item); + + S_LOCK(mh); + + val = remove_all_numbered_files(path); + g_free(path); + if (val == 0) { + item->new = item->unread = item->total = 0; + item->last_num = 0; + item->updated = TRUE; + item->mtime = 0; + } + + S_UNLOCK(mh); + + return val; +} + +static gboolean mh_is_msg_changed(Folder *folder, FolderItem *item, + MsgInfo *msginfo) +{ + GStatBuf s; + gchar buf[16]; + + if (g_stat(utos_buf(buf, msginfo->msgnum), &s) < 0 || + msginfo->size != s.st_size || + msginfo->mtime != s.st_mtime) + return TRUE; + + return FALSE; +} + +static gint mh_close(Folder *folder, FolderItem *item) +{ + return 0; +} + +#ifdef G_OS_WIN32 +struct wfddata { + WIN32_FIND_DATAA wfda; + WIN32_FIND_DATAW wfdw; + DWORD file_attr; + gchar *file_name; +}; + +static HANDLE find_first_file(const gchar *path, struct wfddata *wfd) +{ + HANDLE hfind; + + if (G_WIN32_HAVE_WIDECHAR_API()) { + if (path) { + gchar *wildcard_path; + wchar_t *wpath; + + wildcard_path = g_strconcat(path, "\\*", NULL); + wpath = g_utf8_to_utf16(wildcard_path, -1, + NULL, NULL, NULL); + if (wpath) { + hfind = FindFirstFileW(wpath, &wfd->wfdw); + g_free(wpath); + } else + hfind = INVALID_HANDLE_VALUE; + g_free(wildcard_path); + } else + hfind = FindFirstFileW(L"*", &wfd->wfdw); + + if (hfind != INVALID_HANDLE_VALUE) { + wfd->file_attr = wfd->wfdw.dwFileAttributes; + wfd->file_name = g_utf16_to_utf8(wfd->wfdw.cFileName, -1, + NULL, NULL, NULL); + } + } else { + if (path) { + gchar *wildcard_path; + gchar *cp_path; + + wildcard_path = g_strconcat(path, "\\*", NULL); + cp_path = g_locale_from_utf8(wildcard_path, -1, + NULL, NULL, NULL); + if (cp_path) { + hfind = FindFirstFileA(cp_path, &wfd->wfda); + g_free(cp_path); + } else + hfind = INVALID_HANDLE_VALUE; + g_free(wildcard_path); + } else + hfind = FindFirstFileA("*", &wfd->wfda); + + if (hfind != INVALID_HANDLE_VALUE) { + wfd->file_attr = wfd->wfda.dwFileAttributes; + wfd->file_name = g_locale_to_utf8(wfd->wfda.cFileName, + -1, NULL, NULL, NULL); + } + } + + return hfind; +} + +static BOOL find_next_file(HANDLE hfind, struct wfddata *wfd) +{ + BOOL retval; + + if (G_WIN32_HAVE_WIDECHAR_API()) { + retval = FindNextFileW(hfind, &wfd->wfdw); + if (retval) { + wfd->file_attr = wfd->wfdw.dwFileAttributes; + wfd->file_name = g_utf16_to_utf8(wfd->wfdw.cFileName, -1, + NULL, NULL, NULL); + } + } else { + retval = FindNextFileA(hfind, &wfd->wfda); + if (retval) { + wfd->file_attr = wfd->wfda.dwFileAttributes; + wfd->file_name = g_locale_to_utf8(wfd->wfda.cFileName, + -1, NULL, NULL, NULL); + } + } + + return retval; +} +#endif + +static gint mh_scan_folder_full(Folder *folder, FolderItem *item, + gboolean count_sum) +{ + gchar *path; +#ifdef G_OS_WIN32 + struct wfddata wfd; + HANDLE hfind; +#else + DIR *dp; + struct dirent *d; +#endif + gint max = 0; + gint num; + gint n_msg = 0; + + g_return_val_if_fail(item != NULL, -1); + + debug_print("mh_scan_folder(): Scanning %s ...\n", item->path); + + S_LOCK(mh); + + path = folder_item_get_path(item); + if (!path) { + S_UNLOCK(mh); + return -1; + } + if (change_dir(path) < 0) { + g_free(path); + S_UNLOCK(mh); + return -1; + } + g_free(path); + +#ifdef G_OS_WIN32 + if ((hfind = find_first_file(NULL, &wfd)) == INVALID_HANDLE_VALUE) { + g_warning("failed to open directory\n"); +#else + if ((dp = opendir(".")) == NULL) { + FILE_OP_ERROR(item->path, "opendir"); +#endif + S_UNLOCK(mh); + return -1; + } + + if (folder->ui_func) + folder->ui_func(folder, item, folder->ui_func_data); + +#ifdef G_OS_WIN32 + do { + if ((wfd.file_attr & + (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE)) == 0) { + if (wfd.file_name) { + if ((num = to_number(wfd.file_name)) > 0) { + n_msg++; + if (max < num) + max = num; + } + } + } + + if (wfd.file_name) { + g_free(wfd.file_name); + wfd.file_name = NULL; + } + } while (find_next_file(hfind, &wfd)); + + FindClose(hfind); +#else + while ((d = readdir(dp)) != NULL) { + if ((num = to_number(d->d_name)) > 0 && + dirent_is_regular_file(d)) { + n_msg++; + if (max < num) + max = num; + } + } + + closedir(dp); +#endif + + if (n_msg == 0) + item->new = item->unread = item->total = 0; + else if (count_sum) { + gint new, unread, total, min, max_; + + procmsg_get_mark_sum + (item, &new, &unread, &total, &min, &max_, 0); + + if (n_msg > total) { + item->unmarked_num = new = n_msg - total; + unread += n_msg - total; + } else + item->unmarked_num = 0; + + item->new = new; + item->unread = unread; + item->total = n_msg; + + if (item->cache_queue && !item->opened) { + procmsg_flush_cache_queue(item, NULL); + } + } + + item->updated = TRUE; + item->mtime = 0; + + debug_print("Last number in dir %s = %d\n", item->path, max); + item->last_num = max; + + S_UNLOCK(mh); + return 0; +} + +static gint mh_scan_folder(Folder *folder, FolderItem *item) +{ + return mh_scan_folder_full(folder, item, TRUE); +} + +static gint mh_scan_tree(Folder *folder) +{ + FolderItem *item; + gchar *rootpath; + + g_return_val_if_fail(folder != NULL, -1); + + S_LOCK(mh); + + if (!folder->node) { + item = folder_item_new(folder->name, NULL); + item->folder = folder; + folder->node = item->node = g_node_new(item); + } else + item = FOLDER_ITEM(folder->node->data); + + rootpath = folder_item_get_path(item); + if (change_dir(rootpath) < 0) { + g_free(rootpath); + S_UNLOCK(mh); + return -1; + } + g_free(rootpath); + + mh_create_tree(folder); + mh_remove_missing_folder_items(folder); + mh_scan_tree_recursive(item); + + S_UNLOCK(mh); + return 0; +} + +#define MAKE_DIR_IF_NOT_EXIST(dir) \ +{ \ + if (!is_dir_exist(dir)) { \ + if (is_file_exist(dir)) { \ + g_warning(_("File `%s' already exists.\n" \ + "Can't create folder."), dir); \ + return -1; \ + } \ + if (make_dir(dir) < 0) \ + return -1; \ + } \ +} + +#define MAKE_DIR_HIER_IF_NOT_EXIST(dir) \ +{ \ + if (!is_dir_exist(dir)) { \ + if (is_file_exist(dir)) { \ + g_warning(_("File `%s' already exists.\n" \ + "Can't create folder."), dir); \ + return -1; \ + } \ + if (make_dir_hier(dir) < 0) \ + return -1; \ + } \ +} + +static gint mh_create_tree(Folder *folder) +{ + gchar *rootpath; + + g_return_val_if_fail(folder != NULL, -1); + + CHDIR_RETURN_VAL_IF_FAIL(get_mail_base_dir(), -1); + rootpath = LOCAL_FOLDER(folder)->rootpath; + MAKE_DIR_HIER_IF_NOT_EXIST(rootpath); + CHDIR_RETURN_VAL_IF_FAIL(rootpath, -1); + MAKE_DIR_IF_NOT_EXIST(INBOX_DIR); + MAKE_DIR_IF_NOT_EXIST(OUTBOX_DIR); + MAKE_DIR_IF_NOT_EXIST(QUEUE_DIR); + MAKE_DIR_IF_NOT_EXIST(DRAFT_DIR); + MAKE_DIR_IF_NOT_EXIST(TRASH_DIR); + MAKE_DIR_IF_NOT_EXIST(JUNK_DIR); + + return 0; +} + +#undef MAKE_DIR_IF_NOT_EXIST +#undef MAKE_DIR_HIER_IF_NOT_EXIST + +static FolderItem *mh_create_folder(Folder *folder, FolderItem *parent, + const gchar *name) +{ + gchar *path; + gchar *fs_name; + gchar *fullpath; + FolderItem *new_item; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(parent != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + S_LOCK(mh); + + path = folder_item_get_path(parent); + fs_name = g_filename_from_utf8(name, -1, NULL, NULL, NULL); + fullpath = g_strconcat(path, G_DIR_SEPARATOR_S, + fs_name ? fs_name : name, NULL); + g_free(fs_name); + g_free(path); + + if (make_dir_hier(fullpath) < 0) { + g_free(fullpath); + S_UNLOCK(mh); + return NULL; + } + + g_free(fullpath); + + /* path is a logical folder path */ + if (parent->path) + path = g_strconcat(parent->path, "/", name, NULL); + else + path = g_strdup(name); + new_item = folder_item_new(name, path); + folder_item_append(parent, new_item); + g_free(path); + + S_UNLOCK(mh); + return new_item; +} + +static gint mh_move_folder_real(Folder *folder, FolderItem *item, + FolderItem *new_parent, const gchar *name) +{ + gchar *rootpath; + gchar *oldpath; + gchar *newpath; + gchar *dirname; + gchar *new_dir; + gchar *name_; + gchar *utf8_name; + gchar *paths[2]; + gchar *old_id, *new_id; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(folder == item->folder, -1); + g_return_val_if_fail(item->path != NULL, -1); + g_return_val_if_fail(new_parent != NULL || name != NULL, -1); + if (new_parent) { + g_return_val_if_fail(item != new_parent, -1); + g_return_val_if_fail(item->parent != new_parent, -1); + g_return_val_if_fail(item->folder == new_parent->folder, -1); + if (g_node_is_ancestor(item->node, new_parent->node)) { + g_warning("folder to be moved is ancestor of new parent\n"); + return -1; + } + } + + S_LOCK(mh); + + oldpath = folder_item_get_path(item); + if (new_parent) { + if (name) { + name_ = g_filename_from_utf8(name, -1, NULL, NULL, + NULL); + if (!name_) + name_ = g_strdup(name); + utf8_name = g_strdup(name); + } else { + name_ = g_path_get_basename(oldpath); + utf8_name = g_filename_to_utf8(name_, -1, NULL, NULL, + NULL); + if (!utf8_name) + utf8_name = g_strdup(name_); + } + new_dir = folder_item_get_path(new_parent); + newpath = g_strconcat(new_dir, G_DIR_SEPARATOR_S, name_, NULL); + g_free(new_dir); + } else { + name_ = g_filename_from_utf8(name, -1, NULL, NULL, NULL); + utf8_name = g_strdup(name); + dirname = g_dirname(oldpath); + newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, + name_ ? name_ : name, NULL); + g_free(dirname); + } + g_free(name_); + + if (is_file_entry_exist(newpath)) { + g_warning("%s already exists\n", newpath); + g_free(oldpath); + g_free(newpath); + g_free(utf8_name); + S_UNLOCK(mh); + return -1; + } + + rootpath = folder_get_path(folder); + if (change_dir(rootpath) < 0) { + g_free(rootpath); + g_free(oldpath); + g_free(newpath); + g_free(utf8_name); + S_UNLOCK(mh); + return -1; + } + g_free(rootpath); + + debug_print("mh_move_folder: rename(%s, %s)\n", oldpath, newpath); + + if (g_rename(oldpath, newpath) < 0) { + FILE_OP_ERROR(oldpath, "rename"); + g_free(oldpath); + g_free(newpath); + g_free(utf8_name); + S_UNLOCK(mh); + return -1; + } + + g_free(oldpath); + g_free(newpath); + + old_id = folder_item_get_identifier(item); + + if (new_parent) { + g_node_unlink(item->node); + g_node_append(new_parent->node, item->node); + item->parent = new_parent; + if (new_parent->path != NULL) { + newpath = g_strconcat(new_parent->path, "/", utf8_name, + NULL); + g_free(utf8_name); + } else + newpath = utf8_name; + } else { + if (strchr(item->path, '/') != NULL) { + dirname = g_dirname(item->path); + newpath = g_strconcat(dirname, "/", utf8_name, NULL); + g_free(dirname); + g_free(utf8_name); + } else + newpath = utf8_name; + } + + if (name) { + g_free(item->name); + item->name = g_strdup(name); + } + + paths[0] = g_strdup(item->path); + paths[1] = newpath; + g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + mh_rename_folder_func, paths); + + g_free(paths[0]); + g_free(paths[1]); + + new_id = folder_item_get_identifier(item); + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "move-folder", item, + old_id, new_id); + g_free(new_id); + g_free(old_id); + + S_UNLOCK(mh); + return 0; +} + +static gint mh_move_folder(Folder *folder, FolderItem *item, + FolderItem *new_parent) +{ + return mh_move_folder_real(folder, item, new_parent, NULL); +} + +static gint mh_rename_folder(Folder *folder, FolderItem *item, + const gchar *name) +{ + return mh_move_folder_real(folder, item, NULL, name); +} + +static gint mh_remove_folder(Folder *folder, FolderItem *item) +{ + gchar *path; + + 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); + + S_LOCK(mh); + + path = folder_item_get_path(item); + if (remove_dir_recursive(path) < 0) { + g_warning("can't remove directory `%s'\n", path); + g_free(path); + S_UNLOCK(mh); + return -1; + } + + g_free(path); + if (syl_app_get()) + g_signal_emit_by_name(syl_app_get(), "remove-folder", item); + folder_item_remove(item); + + S_UNLOCK(mh); + return 0; +} + + +static time_t mh_get_mtime(FolderItem *item) +{ + gchar *path; + GStatBuf s; + + path = folder_item_get_path(item); + if (g_stat(path, &s) < 0) { + FILE_OP_ERROR(path, "stat"); + g_free(path); + return -1; + } else { + g_free(path); + return MAX(s.st_mtime, s.st_ctime); + } +} + +static GSList *mh_get_uncached_msgs(GHashTable *msg_table, FolderItem *item) +{ + gchar *path; + GDir *dp; + const gchar *dir_name; + GSList *newlist = NULL; + GSList *last = NULL; + MsgInfo *msginfo; + gint n_newmsg = 0; + gint num; + Folder *folder; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->folder != NULL, NULL); + + folder = item->folder; + + path = folder_item_get_path(item); + g_return_val_if_fail(path != NULL, NULL); + if (change_dir(path) < 0) { + g_free(path); + return NULL; + } + g_free(path); + + if ((dp = g_dir_open(".", 0, NULL)) == NULL) { + FILE_OP_ERROR(item->path, "opendir"); + return NULL; + } + + debug_print("Searching uncached messages...\n"); + + if (msg_table) { + gint count = 0; + + while ((dir_name = g_dir_read_name(dp)) != NULL) { + if ((num = to_number(dir_name)) <= 0) continue; + + msginfo = g_hash_table_lookup + (msg_table, GUINT_TO_POINTER(num)); + + if (msginfo) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_CACHED); + } else { + /* not found in the cache (uncached message) */ + msginfo = mh_parse_msg(dir_name, item); + if (!msginfo) continue; + + if (!newlist) + last = newlist = + g_slist_append(NULL, msginfo); + else { + last = g_slist_append(last, msginfo); + last = last->next; + } + n_newmsg++; + } + + count++; + if (folder->ui_func) + folder->ui_func(folder, item, folder->ui_func_data ? folder->ui_func_data : GINT_TO_POINTER(count)); + } + } else { + /* discard all previous cache */ + while ((dir_name = g_dir_read_name(dp)) != NULL) { + if (to_number(dir_name) <= 0) continue; + + msginfo = mh_parse_msg(dir_name, item); + if (!msginfo) continue; + + if (!newlist) + last = newlist = g_slist_append(NULL, msginfo); + else { + last = g_slist_append(last, msginfo); + last = last->next; + } + n_newmsg++; + if (folder->ui_func) + folder->ui_func(folder, item, folder->ui_func_data ? folder->ui_func_data : GINT_TO_POINTER(n_newmsg)); + } + } + + g_dir_close(dp); + + if (n_newmsg) + debug_print("%d uncached message(s) found.\n", n_newmsg); + else + debug_print("done.\n"); + + /* sort new messages in numerical order */ + if (newlist && item->sort_key == SORT_BY_NONE) { + debug_print("Sorting uncached messages in numerical order...\n"); + newlist = g_slist_sort + (newlist, (GCompareFunc)procmsg_cmp_msgnum_for_sort); + debug_print("done.\n"); + } + + return newlist; +} + +static MsgInfo *mh_parse_msg(const gchar *file, FolderItem *item) +{ + MsgInfo *msginfo; + MsgFlags flags; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(file != NULL, NULL); + + flags.perm_flags = MSG_NEW|MSG_UNREAD; + flags.tmp_flags = 0; + + 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); + } + + msginfo = procheader_parse_file(file, flags, FALSE); + if (!msginfo) return NULL; + + msginfo->msgnum = atoi(file); + msginfo->folder = item; + + return msginfo; +} + +#if 0 +static gboolean mh_is_maildir_one(const gchar *path, const gchar *dir) +{ + gchar *entry; + gboolean result; + + entry = g_strconcat(path, G_DIR_SEPARATOR_S, dir, NULL); + result = is_dir_exist(entry); + g_free(entry); + + return result; +} + +/* + * check whether PATH is a Maildir style mailbox. + * This is the case if the 3 subdir: new, cur, tmp are existing. + * This functon assumes that entry is an directory + */ +static gboolean mh_is_maildir(const gchar *path) +{ + return mh_is_maildir_one(path, "new") && + mh_is_maildir_one(path, "cur") && + mh_is_maildir_one(path, "tmp"); +} +#endif + +static gboolean mh_remove_missing_folder_items_func(GNode *node, gpointer data) +{ + FolderItem *item; + gchar *path; + + g_return_val_if_fail(node->data != NULL, FALSE); + + if (G_NODE_IS_ROOT(node)) + return FALSE; + + item = FOLDER_ITEM(node->data); + +#if 0 + if (item->path && strchr(item->path, '/')) { + debug_print("folder '%s' includes Unix path separator. removing...\n", item->path); + folder_item_remove(item); + return FALSE; + } +#endif + + path = folder_item_get_path(item); + if (!is_dir_exist(path)) { + debug_print("folder '%s' not found. removing...\n", path); + folder_item_remove(item); + } + g_free(path); + + return FALSE; +} + +static void mh_remove_missing_folder_items(Folder *folder) +{ + g_return_if_fail(folder != NULL); + + debug_print("searching missing folders...\n"); + + g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1, + mh_remove_missing_folder_items_func, folder); +} + +#define MAX_RECURSION_LEVEL 64 + +static void mh_scan_tree_recursive(FolderItem *item) +{ + Folder *folder; +#ifdef G_OS_WIN32 + struct wfddata wfd; + HANDLE hfind; +#else + DIR *dp; + struct dirent *d; + GStatBuf s; +#endif + const gchar *dir_name; + gchar *fs_path; + gchar *entry; + gchar *utf8entry; + gchar *utf8name; + gint n_msg = 0; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + if (item->stype == F_VIRTUAL) + return; + + folder = item->folder; + + if (g_node_depth(item->node) >= MAX_RECURSION_LEVEL) { + g_warning("mh_scan_tree_recursive(): max recursion level (%u) reached.", MAX_RECURSION_LEVEL); + return; + } + + debug_print("scanning %s ...\n", + item->path ? item->path : LOCAL_FOLDER(folder)->rootpath); + if (folder->ui_func) + folder->ui_func(folder, item, folder->ui_func_data); + + fs_path = item->path ? + g_filename_from_utf8(item->path, -1, NULL, NULL, NULL) + : g_strdup("."); + if (!fs_path) + fs_path = g_strdup(item->path); +#ifdef G_OS_WIN32 + hfind = find_first_file(fs_path, &wfd); + if (hfind == INVALID_HANDLE_VALUE) { + g_warning("failed to open directory: %s\n", fs_path); + g_free(fs_path); + return; + } +#else + dp = opendir(fs_path); + if (!dp) { + FILE_OP_ERROR(fs_path, "opendir"); + g_free(fs_path); + return; + } +#endif + g_free(fs_path); + +#ifdef G_OS_WIN32 + do { + if (!wfd.file_name) continue; + if (wfd.file_name[0] == '.') { + g_free(wfd.file_name); + wfd.file_name = NULL; + continue; + } + dir_name = utf8name = wfd.file_name; + wfd.file_name = NULL; +#else + while ((d = readdir(dp)) != NULL) { + dir_name = d->d_name; + if (dir_name[0] == '.') continue; + + utf8name = g_filename_to_utf8(dir_name, -1, NULL, NULL, NULL); + if (!utf8name) + utf8name = g_strdup(dir_name); +#endif + + if (item->path) { + utf8entry = g_strconcat(item->path, "/", utf8name, + NULL); + } else + utf8entry = g_strdup(utf8name); + entry = g_filename_from_utf8(utf8entry, -1, NULL, NULL, NULL); + if (!entry) + entry = g_strdup(utf8entry); +#ifdef G_OS_WIN32 + subst_char(entry, '/', G_DIR_SEPARATOR); +#endif + + if ( +#ifdef G_OS_WIN32 + (wfd.file_attr & FILE_ATTRIBUTE_DIRECTORY) != 0 +#else +#if HAVE_DIRENT_D_TYPE + d->d_type == DT_DIR || + ((d->d_type == DT_UNKNOWN || d->d_type == DT_LNK) && +#endif + g_stat(entry, &s) == 0 && S_ISDIR(s.st_mode) +#if HAVE_DIRENT_D_TYPE + ) +#endif +#endif /* G_OS_WIN32 */ + ) { + FolderItem *new_item = NULL; + GNode *node; + +#ifndef G_OS_WIN32 + if (g_utf8_validate(utf8name, -1, NULL) == FALSE) { + g_warning(_("Directory name\n" + "'%s' is not a valid UTF-8 string.\n" + "Maybe the locale encoding is used for filename.\n" + "If that is the case, you must set the following environmental variable\n" + "(see README for detail):\n" + "\n" + "\tG_FILENAME_ENCODING=@locale\n"), + utf8name); + g_free(entry); + g_free(utf8entry); + g_free(utf8name); + continue; + } +#endif /* G_OS_WIN32 */ + + node = item->node; + for (node = node->children; node != NULL; node = node->next) { + FolderItem *cur_item = FOLDER_ITEM(node->data); + if (!strcmp2(cur_item->path, utf8entry)) { + new_item = cur_item; + break; + } + } + if (!new_item) { + debug_print("new folder '%s' found.\n", + utf8entry); + new_item = folder_item_new(utf8name, utf8entry); + folder_item_append(item, new_item); + } + + if (!item->path) { + if (!folder->inbox && + !strcmp(dir_name, INBOX_DIR)) { + new_item->stype = F_INBOX; + folder->inbox = new_item; + } else if (!folder->outbox && + !strcmp(dir_name, OUTBOX_DIR)) { + new_item->stype = F_OUTBOX; + folder->outbox = new_item; + } else if (!folder->draft && + !strcmp(dir_name, DRAFT_DIR)) { + new_item->stype = F_DRAFT; + folder->draft = new_item; + } else if (!folder->queue && + !strcmp(dir_name, QUEUE_DIR)) { + new_item->stype = F_QUEUE; + folder->queue = new_item; + } else if (!folder->trash && + !strcmp(dir_name, TRASH_DIR)) { + new_item->stype = F_TRASH; + folder->trash = new_item; + } else if (!folder_get_junk(folder) && + !strcmp(dir_name, JUNK_DIR)) { + new_item->stype = F_JUNK; + folder_set_junk(folder, new_item); + } + } + + mh_scan_tree_recursive(new_item); + } else if (to_number(dir_name) > 0) n_msg++; + + g_free(entry); + g_free(utf8entry); + g_free(utf8name); +#ifdef G_OS_WIN32 + } while (find_next_file(hfind, &wfd)); +#else + } +#endif + +#ifdef G_OS_WIN32 + FindClose(hfind); +#else + closedir(dp); +#endif + + if (item->path) { + gint new, unread, total, min, max; + + procmsg_get_mark_sum + (item, &new, &unread, &total, &min, &max, 0); + if (n_msg > total) { + new += n_msg - total; + unread += n_msg - total; + } + item->new = new; + item->unread = unread; + item->total = n_msg; + item->updated = TRUE; + item->mtime = 0; + } +} + +static gboolean mh_rename_folder_func(GNode *node, gpointer data) +{ + FolderItem *item = node->data; + gchar **paths = data; + const gchar *oldpath = paths[0]; + const gchar *newpath = paths[1]; + gchar *base; + gchar *new_itempath; + gint oldpathlen; + + oldpathlen = strlen(oldpath); + if (strncmp(oldpath, item->path, oldpathlen) != 0) { + g_warning("path doesn't match: %s, %s\n", oldpath, item->path); + return TRUE; + } + + base = item->path + oldpathlen; + while (*base == '/') base++; + if (*base == '\0') + new_itempath = g_strdup(newpath); + else + new_itempath = g_strconcat(newpath, "/", base, NULL); + g_free(item->path); + item->path = new_itempath; + + return FALSE; +} diff --git a/libsylph/mh.h b/libsylph/mh.h new file mode 100644 index 0000000..32cb590 --- /dev/null +++ b/libsylph/mh.h @@ -0,0 +1,38 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2007 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __MH_H__ +#define __MH_H__ + +#include + +#include "folder.h" + +typedef struct _MHFolder MHFolder; + +#define MH_FOLDER(obj) ((MHFolder *)obj) + +struct _MHFolder +{ + LocalFolder lfolder; +}; + +FolderClass *mh_get_class (void); + +#endif /* __MH_H__ */ diff --git a/libsylph/news.c b/libsylph/news.c new file mode 100644 index 0000000..ffff9f9 --- /dev/null +++ b/libsylph/news.c @@ -0,0 +1,1110 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2012 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "news.h" +#include "nntp.h" +#include "socket.h" +#include "recv.h" +#include "procmsg.h" +#include "procheader.h" +#include "folder.h" +#include "session.h" +#include "codeconv.h" +#include "utils.h" +#include "prefs_common.h" +#include "prefs_account.h" +#if USE_SSL +# include "ssl.h" +#endif +#include "socks.h" + +#define NNTP_PORT 119 +#if USE_SSL +#define NNTPS_PORT 563 +#endif + +static void news_folder_init (Folder *folder, + const gchar *name, + const gchar *path); + +static Folder *news_folder_new (const gchar *name, + const gchar *folder); +static void news_folder_destroy (Folder *folder); + +static GSList *news_get_article_list (Folder *folder, + FolderItem *item, + gboolean use_cache); +static gchar *news_fetch_msg (Folder *folder, + FolderItem *item, + gint num); +static MsgInfo *news_get_msginfo (Folder *folder, + FolderItem *item, + gint num); + +static gint news_close (Folder *folder, + FolderItem *item); + +static gint news_scan_group (Folder *folder, + FolderItem *item); + +#if USE_SSL +static Session *news_session_new (const gchar *server, + gushort port, + SocksInfo *socks_info, + const gchar *userid, + const gchar *passwd, + SSLType ssl_type); +#else +static Session *news_session_new (const gchar *server, + gushort port, + SocksInfo *socks_info, + const gchar *userid, + const gchar *passwd); +#endif + +static gint news_get_article_cmd (NNTPSession *session, + const gchar *cmd, + gint num, + gchar *filename); +static gint news_get_article (NNTPSession *session, + gint num, + gchar *filename); +#if 0 +static gint news_get_header (NNTPSession *session, + gint num, + gchar *filename); +#endif + +static gint news_select_group (NNTPSession *session, + const gchar *group, + gint *num, + gint *first, + gint *last); +static GSList *news_get_uncached_articles(NNTPSession *session, + FolderItem *item, + gint cache_last, + gint *rfirst, + gint *rlast); +static MsgInfo *news_parse_xover (const gchar *xover_str); +static gchar *news_parse_xhdr (const gchar *xhdr_str, + MsgInfo *msginfo); +static GSList *news_delete_old_articles (GSList *alist, + FolderItem *item, + gint first); +static void news_delete_all_articles (FolderItem *item); +static void news_delete_expired_caches (GSList *alist, + FolderItem *item); + +static FolderClass news_class = +{ + F_NEWS, + + news_folder_new, + news_folder_destroy, + + NULL, + NULL, + + news_get_article_list, + NULL, + news_fetch_msg, + news_get_msginfo, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + news_close, + news_scan_group, + + NULL, + NULL, + NULL, + NULL +}; + + +FolderClass *news_get_class(void) +{ + return &news_class; +} + +static Folder *news_folder_new(const gchar *name, const gchar *path) +{ + Folder *folder; + + folder = (Folder *)g_new0(NewsFolder, 1); + news_folder_init(folder, name, path); + + return folder; +} + +static void news_folder_destroy(Folder *folder) +{ + if (REMOTE_FOLDER(folder)->remove_cache_on_destroy) { + gchar *dir; + gchar *server; + + dir = folder_get_path(folder); + if (is_dir_exist(dir)) + remove_dir_recursive(dir); + g_free(dir); + + server = uriencode_for_filename(folder->account->nntp_server); + dir = g_strconcat(get_news_cache_dir(), G_DIR_SEPARATOR_S, + server, NULL); + if (is_dir_exist(dir)) + g_rmdir(dir); + g_free(dir); + g_free(server); + } + + folder_remote_folder_destroy(REMOTE_FOLDER(folder)); +} + +static void news_folder_init(Folder *folder, const gchar *name, + const gchar *path) +{ + folder->klass = news_get_class(); + folder_remote_folder_init(folder, name, path); +} + +#if USE_SSL +static Session *news_session_new(const gchar *server, gushort port, + SocksInfo *socks_info, + const gchar *userid, const gchar *passwd, + SSLType ssl_type) +#else +static Session *news_session_new(const gchar *server, gushort port, + SocksInfo *socks_info, + const gchar *userid, const gchar *passwd) +#endif +{ + gchar buf[NNTPBUFSIZE]; + Session *session; + + g_return_val_if_fail(server != NULL, NULL); + + log_message(_("creating NNTP connection to %s:%d ...\n"), server, port); + +#if USE_SSL + session = nntp_session_new_full(server, port, socks_info, buf, userid, passwd, ssl_type); +#else + session = nntp_session_new_full(server, port, socks_info, buf, userid, passwd); +#endif + + return session; +} + +static Session *news_session_new_for_folder(Folder *folder) +{ + Session *session; + PrefsAccount *ac; + SocksInfo *socks_info = NULL; + const gchar *userid = NULL; + gchar *passwd = NULL; + gushort port; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(folder->account != NULL, NULL); + + ac = folder->account; + if (ac->use_nntp_auth && ac->userid && ac->userid[0]) { + userid = ac->userid; + if (ac->passwd && ac->passwd[0]) + passwd = g_strdup(ac->passwd); + else + passwd = input_query_password(ac->nntp_server, userid); + } + + if (ac->use_socks && ac->use_socks_for_recv && ac->proxy_host) { + socks_info = socks_info_new(ac->socks_type, ac->proxy_host, ac->proxy_port, ac->use_proxy_auth ? ac->proxy_name : NULL, ac->use_proxy_auth ? ac->proxy_pass : NULL); + } + +#if USE_SSL + port = ac->set_nntpport ? ac->nntpport + : ac->ssl_nntp ? NNTPS_PORT : NNTP_PORT; + session = news_session_new(ac->nntp_server, port, socks_info, + userid, passwd, ac->ssl_nntp); +#else + port = ac->set_nntpport ? ac->nntpport : NNTP_PORT; + session = news_session_new(ac->nntp_server, port, socks_info, + userid, passwd); +#endif + + if (socks_info) + socks_info_free(socks_info); + + g_free(passwd); + + return session; +} + +static NNTPSession *news_session_get(Folder *folder) +{ + RemoteFolder *rfolder = REMOTE_FOLDER(folder); + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL); + g_return_val_if_fail(folder->account != NULL, NULL); + + if (!prefs_common.online_mode) + return NULL; + + if (!rfolder->session) { + rfolder->session = news_session_new_for_folder(folder); + return NNTP_SESSION(rfolder->session); + } + + if (time(NULL) - rfolder->session->last_access_time < + SESSION_TIMEOUT_INTERVAL) { + return NNTP_SESSION(rfolder->session); + } + + if (nntp_mode(NNTP_SESSION(rfolder->session), FALSE) + != NN_SUCCESS) { + log_warning(_("NNTP connection to %s:%d has been" + " disconnected. Reconnecting...\n"), + folder->account->nntp_server, + folder->account->set_nntpport ? + folder->account->nntpport : NNTP_PORT); + session_destroy(rfolder->session); + rfolder->session = news_session_new_for_folder(folder); + } + + if (rfolder->session) + session_set_access_time(rfolder->session); + + return NNTP_SESSION(rfolder->session); +} + +static GSList *news_get_article_list(Folder *folder, FolderItem *item, + gboolean use_cache) +{ + GSList *alist; + NNTPSession *session; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL); + + session = news_session_get(folder); + + if (!session) { + alist = procmsg_read_cache(item, FALSE); + item->last_num = procmsg_get_last_num_in_msg_list(alist); + } else if (use_cache) { + GSList *newlist; + gint cache_last; + gint first, last; + + alist = procmsg_read_cache(item, FALSE); + + cache_last = procmsg_get_last_num_in_msg_list(alist); + newlist = news_get_uncached_articles + (session, item, cache_last, &first, &last); + if (newlist) + item->cache_dirty = TRUE; + if (first == 0 && last == 0) { + news_delete_all_articles(item); + procmsg_msg_list_free(alist); + alist = NULL; + item->cache_dirty = TRUE; + } else { + alist = news_delete_old_articles(alist, item, first); + news_delete_expired_caches(alist, item); + } + + alist = g_slist_concat(alist, newlist); + + item->last_num = last; + } else { + gint last; + + alist = news_get_uncached_articles + (session, item, 0, NULL, &last); + news_delete_all_articles(item); + item->last_num = last; + item->cache_dirty = TRUE; + } + + procmsg_set_flags(alist, item); + + alist = procmsg_sort_msg_list(alist, item->sort_key, item->sort_type); + + if (item->mark_queue) + item->mark_dirty = TRUE; + + debug_print("cache_dirty: %d, mark_dirty: %d\n", + item->cache_dirty, item->mark_dirty); + + if (!item->opened) { + if (item->cache_dirty) + procmsg_write_cache_list(item, alist); + if (item->mark_dirty) + procmsg_write_flags_list(item, alist); + } + + return alist; +} + +static gchar *news_fetch_msg(Folder *folder, FolderItem *item, gint num) +{ + gchar *path, *filename; + NNTPSession *session; + gchar nstr[16]; + gint ok; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + + path = folder_item_get_path(item); + if (!is_dir_exist(path)) + make_dir_hier(path); + filename = g_strconcat(path, G_DIR_SEPARATOR_S, utos_buf(nstr, num), + NULL); + g_free(path); + + if (is_file_exist(filename) && get_file_size(filename) > 0) { + debug_print(_("article %d has been already cached.\n"), num); + return filename; + } + + session = news_session_get(folder); + if (!session) { + g_free(filename); + return NULL; + } + + ok = news_select_group(session, item->path, NULL, NULL, NULL); + if (ok != NN_SUCCESS) { + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(folder)->session = NULL; + } + g_free(filename); + return NULL; + } + + debug_print(_("getting article %d...\n"), num); + ok = news_get_article(NNTP_SESSION(REMOTE_FOLDER(folder)->session), + num, filename); + if (ok != NN_SUCCESS) { + g_warning(_("can't read article %d\n"), num); + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(folder)->session = NULL; + } + g_free(filename); + return NULL; + } + + return filename; +} + +static MsgInfo *news_get_msginfo(Folder *folder, FolderItem *item, gint num) +{ + MsgInfo *msginfo; + MsgFlags flags = {0, 0}; + gchar *file; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + + file = news_fetch_msg(folder, item, num); + if (!file) return NULL; + + msginfo = procheader_parse_file(file, flags, FALSE); + + g_free(file); + + return msginfo; +} + +static gint news_close(Folder *folder, FolderItem *item) +{ + return 0; +} + +static gint news_scan_group(Folder *folder, FolderItem *item) +{ + NNTPSession *session; + gint num = 0, first = 0, last = 0; + gint new = 0, unread = 0, total = 0; + gint min = 0, max = 0; + gint ok; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + + session = news_session_get(folder); + if (!session) return -1; + + ok = news_select_group(session, item->path, &num, &first, &last); + if (ok != NN_SUCCESS) { + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(folder)->session = NULL; + } + return -1; + } + + if (num == 0) { + item->new = item->unread = item->total = item->last_num = 0; + return 0; + } + + procmsg_get_mark_sum(item, &new, &unread, &total, &min, &max, first); + + if (max < first || last < min) + new = unread = total = num; + else { + if (min < first) + min = first; + + if (last < max) + max = last; + else if (max < last) { + new += last - max; + unread += last - max; + } + + if (new > num) new = num; + if (unread > num) unread = num; + } + + item->new = new; + item->unread = unread; + item->total = num; + item->last_num = last; + + return 0; +} + +static NewsGroupInfo *news_group_info_new(const gchar *name, + gint first, gint last, gchar type) +{ + NewsGroupInfo *ginfo; + + ginfo = g_new(NewsGroupInfo, 1); + ginfo->name = g_strdup(name); + ginfo->first = first; + ginfo->last = last; + ginfo->type = type; + ginfo->subscribed = FALSE; + + return ginfo; +} + +static void news_group_info_free(NewsGroupInfo *ginfo) +{ + g_free(ginfo->name); + g_free(ginfo); +} + +static gint news_group_info_compare(NewsGroupInfo *ginfo1, + NewsGroupInfo *ginfo2) +{ + return g_ascii_strcasecmp(ginfo1->name, ginfo2->name); +} + +GSList *news_get_group_list(Folder *folder) +{ + gchar *path, *filename; + FILE *fp; + GSList *list = NULL; + GSList *last = NULL; + gchar buf[NNTPBUFSIZE]; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL); + + path = folder_item_get_path(FOLDER_ITEM(folder->node->data)); + if (!is_dir_exist(path)) + make_dir_hier(path); + filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL); + g_free(path); + + if ((fp = g_fopen(filename, "rb")) == NULL) { + NNTPSession *session; + gint ok; + + session = news_session_get(folder); + if (!session) { + g_free(filename); + return NULL; + } + + ok = nntp_list(session); + if (ok != NN_SUCCESS) { + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(folder)->session = NULL; + } + g_free(filename); + return NULL; + } + if (recv_write_to_file(SESSION(session)->sock, filename) < 0) { + log_warning("can't retrieve newsgroup list\n"); + session_destroy(SESSION(session)); + REMOTE_FOLDER(folder)->session = NULL; + g_free(filename); + return NULL; + } + + if ((fp = g_fopen(filename, "rb")) == NULL) { + FILE_OP_ERROR(filename, "fopen"); + g_free(filename); + return NULL; + } + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + gchar *p = buf; + gchar *name; + gint last_num; + gint first_num; + gchar type; + NewsGroupInfo *ginfo; + + p = strchr(p, ' '); + if (!p) { + strretchomp(buf); + log_warning("invalid LIST response: %s\n", buf); + continue; + } + *p = '\0'; + p++; + name = buf; + + if (sscanf(p, "%d %d %c", &last_num, &first_num, &type) < 3) { + strretchomp(p); + log_warning("invalid LIST response: %s %s\n", name, p); + continue; + } + + ginfo = news_group_info_new(name, first_num, last_num, type); + + if (!last) + last = list = g_slist_append(NULL, ginfo); + else { + last = g_slist_append(last, ginfo); + last = last->next; + } + } + + fclose(fp); + g_free(filename); + + list = g_slist_sort(list, (GCompareFunc)news_group_info_compare); + + return list; +} + +void news_group_list_free(GSList *group_list) +{ + GSList *cur; + + if (!group_list) return; + + for (cur = group_list; cur != NULL; cur = cur->next) + news_group_info_free((NewsGroupInfo *)cur->data); + g_slist_free(group_list); +} + +void news_remove_group_list_cache(Folder *folder) +{ + gchar *path, *filename; + + g_return_if_fail(folder != NULL); + g_return_if_fail(FOLDER_TYPE(folder) == F_NEWS); + + path = folder_item_get_path(FOLDER_ITEM(folder->node->data)); + filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL); + g_free(path); + + if (is_file_exist(filename)) { + if (remove(filename) < 0) + FILE_OP_ERROR(filename, "remove"); + } + g_free(filename); +} + +gint news_post(Folder *folder, const gchar *file) +{ + FILE *fp; + gint ok; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, -1); + g_return_val_if_fail(file != NULL, -1); + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + ok = news_post_stream(folder, fp); + + fclose(fp); + + return ok; +} + +gint news_post_stream(Folder *folder, FILE *fp) +{ + NNTPSession *session; + gint ok; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, -1); + g_return_val_if_fail(fp != NULL, -1); + + session = news_session_get(folder); + if (!session) return -1; + + ok = nntp_post(session, fp); + if (ok != NN_SUCCESS) { + log_warning(_("can't post article.\n")); + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(folder)->session = NULL; + } + return -1; + } + + return 0; +} + +static gint news_get_article_cmd(NNTPSession *session, const gchar *cmd, + gint num, gchar *filename) +{ + gchar *msgid; + gint ok; + + ok = nntp_get_article(session, cmd, num, &msgid); + if (ok != NN_SUCCESS) + return ok; + + debug_print("Message-Id = %s, num = %d\n", msgid, num); + g_free(msgid); + + ok = recv_write_to_file(SESSION(session)->sock, filename); + if (ok < 0) { + log_warning(_("can't retrieve article %d\n"), num); + if (ok == -2) + return NN_SOCKET; + else + return NN_IOERR; + } + + return NN_SUCCESS; +} + +static gint news_get_article(NNTPSession *session, gint num, gchar *filename) +{ + return news_get_article_cmd(session, "ARTICLE", num, filename); +} + +#if 0 +static gint news_get_header(NNTPSession *session, gint num, gchar *filename) +{ + return news_get_article_cmd(session, "HEAD", num, filename); +} +#endif + +/** + * news_select_group: + * @session: Active NNTP session. + * @group: Newsgroup name. + * @num: Estimated number of articles. + * @first: First article number. + * @last: Last article number. + * + * Select newsgroup @group with the GROUP command if it is not already + * selected in @session, or article numbers need to be returned. + * + * Return value: NNTP result code. + **/ +static gint news_select_group(NNTPSession *session, const gchar *group, + gint *num, gint *first, gint *last) +{ + gint ok; + gint num_, first_, last_; + + if (!num || !first || !last) { + if (session->group && + g_ascii_strcasecmp(session->group, group) == 0) + return NN_SUCCESS; + num = &num_; + first = &first_; + last = &last_; + } + + g_free(session->group); + session->group = NULL; + + ok = nntp_group(session, group, num, first, last); + if (ok == NN_SUCCESS) + session->group = g_strdup(group); + else + log_warning(_("can't select group: %s\n"), group); + + return ok; +} + +static GSList *news_get_uncached_articles(NNTPSession *session, + FolderItem *item, gint cache_last, + gint *rfirst, gint *rlast) +{ + gint ok; + gint num = 0, first = 0, last = 0, begin = 0, end = 0; + gchar buf[NNTPBUFSIZE]; + GSList *newlist = NULL; + GSList *llast = NULL; + MsgInfo *msginfo; + gint max_articles; + + if (rfirst) *rfirst = -1; + if (rlast) *rlast = -1; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->folder != NULL, NULL); + g_return_val_if_fail(item->folder->account != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_NEWS, NULL); + + ok = news_select_group(session, item->path, &num, &first, &last); + if (ok != NN_SUCCESS) { + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + } + return NULL; + } + + /* calculate getting overview range */ + if (first > last) { + log_warning(_("invalid article range: %d - %d\n"), + first, last); + return NULL; + } + + if (rfirst) *rfirst = first; + if (rlast) *rlast = last; + + if (cache_last < first) + begin = first; + else if (last < cache_last) + begin = first; + else if (last == cache_last) { + debug_print(_("no new articles.\n")); + return NULL; + } else + begin = cache_last + 1; + end = last; + + max_articles = item->folder->account->max_nntp_articles; + if (max_articles > 0 && end - begin + 1 > max_articles) + begin = end - max_articles + 1; + + log_message(_("getting xover %d - %d in %s...\n"), + begin, end, item->path); + ok = nntp_xover(session, begin, end); + if (ok != NN_SUCCESS) { + log_warning(_("can't get xover\n")); + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + } + return NULL; + } + + for (;;) { + if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) { + log_warning(_("error occurred while getting xover.\n")); + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + return newlist; + } + + if (buf[0] == '.' && buf[1] == '\r') break; + + msginfo = news_parse_xover(buf); + if (!msginfo) { + log_warning(_("invalid xover line: %s\n"), buf); + continue; + } + + msginfo->folder = item; + msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD; + msginfo->flags.tmp_flags = MSG_NEWS; + msginfo->newsgroups = g_strdup(item->path); + + if (!newlist) + llast = newlist = g_slist_append(newlist, msginfo); + else { + llast = g_slist_append(llast, msginfo); + llast = llast->next; + } + } + + ok = nntp_xhdr(session, "to", begin, end); + if (ok != NN_SUCCESS) { + log_warning(_("can't get xhdr\n")); + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + } + return newlist; + } + + llast = newlist; + + for (;;) { + if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) { + log_warning(_("error occurred while getting xhdr.\n")); + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + return newlist; + } + + if (buf[0] == '.' && buf[1] == '\r') break; + if (!llast) { + g_warning("llast == NULL\n"); + continue; + } + + msginfo = (MsgInfo *)llast->data; + msginfo->to = news_parse_xhdr(buf, msginfo); + + llast = llast->next; + } + + ok = nntp_xhdr(session, "cc", begin, end); + if (ok != NN_SUCCESS) { + log_warning(_("can't get xhdr\n")); + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + } + return newlist; + } + + llast = newlist; + + for (;;) { + if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) { + log_warning(_("error occurred while getting xhdr.\n")); + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + return newlist; + } + + if (buf[0] == '.' && buf[1] == '\r') break; + if (!llast) { + g_warning("llast == NULL\n"); + continue; + } + + msginfo = (MsgInfo *)llast->data; + msginfo->cc = news_parse_xhdr(buf, msginfo); + + llast = llast->next; + } + + session_set_access_time(SESSION(session)); + + return newlist; +} + +#define PARSE_ONE_PARAM(p, srcp) \ +{ \ + p = strchr(srcp, '\t'); \ + if (!p) return NULL; \ + else \ + *p++ = '\0'; \ +} + +static MsgInfo *news_parse_xover(const gchar *xover_str) +{ + MsgInfo *msginfo; + gchar *subject, *sender, *size, *line, *date, *msgid, *ref, *tmp; + gchar *p; + gint num, size_int, line_int; + gchar *xover_buf; + + Xstrdup_a(xover_buf, xover_str, return NULL); + + PARSE_ONE_PARAM(subject, xover_buf); + PARSE_ONE_PARAM(sender, subject); + PARSE_ONE_PARAM(date, sender); + PARSE_ONE_PARAM(msgid, date); + PARSE_ONE_PARAM(ref, msgid); + PARSE_ONE_PARAM(size, ref); + PARSE_ONE_PARAM(line, size); + + tmp = strchr(line, '\t'); + if (!tmp) tmp = strchr(line, '\r'); + if (!tmp) tmp = strchr(line, '\n'); + if (tmp) *tmp = '\0'; + + num = atoi(xover_str); + size_int = atoi(size); + line_int = atoi(line); + + /* set MsgInfo */ + msginfo = g_new0(MsgInfo, 1); + msginfo->msgnum = num; + msginfo->size = size_int; + + msginfo->date = g_strdup(date); + msginfo->date_t = procheader_date_parse(NULL, date, 0); + + msginfo->from = conv_unmime_header(sender, NULL); + msginfo->fromname = procheader_get_fromname(msginfo->from); + + msginfo->subject = conv_unmime_header(subject, NULL); + + extract_parenthesis(msgid, '<', '>'); + remove_space(msgid); + if (*msgid != '\0') + msginfo->msgid = g_strdup(msgid); + + eliminate_parenthesis(ref, '(', ')'); + if ((p = strrchr(ref, '<')) != NULL) { + extract_parenthesis(p, '<', '>'); + remove_space(p); + if (*p != '\0') + msginfo->inreplyto = g_strdup(p); + } + + return msginfo; +} + +static gchar *news_parse_xhdr(const gchar *xhdr_str, MsgInfo *msginfo) +{ + gchar *p; + gchar *tmp; + gint num; + + p = strchr(xhdr_str, ' '); + if (!p) + return NULL; + else + p++; + + num = atoi(xhdr_str); + if (msginfo->msgnum != num) return NULL; + + tmp = strchr(p, '\r'); + if (!tmp) tmp = strchr(p, '\n'); + + if (tmp) + return g_strndup(p, tmp - p); + else + return g_strdup(p); +} + +static GSList *news_delete_old_articles(GSList *alist, FolderItem *item, + gint first) +{ + GSList *cur, *next; + MsgInfo *msginfo; + gchar *dir; + + g_return_val_if_fail(item != NULL, alist); + g_return_val_if_fail(item->folder != NULL, alist); + g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_NEWS, alist); + + if (first < 2) return alist; + + debug_print("Deleting cached articles 1 - %d ...\n", first - 1); + + dir = folder_item_get_path(item); + remove_numbered_files(dir, 1, first - 1); + g_free(dir); + + for (cur = alist; cur != NULL; ) { + next = cur->next; + + msginfo = (MsgInfo *)cur->data; + if (msginfo && msginfo->msgnum < first) { + procmsg_msginfo_free(msginfo); + alist = g_slist_remove(alist, msginfo); + item->cache_dirty = TRUE; + } + + cur = next; + } + + return alist; +} + +static void news_delete_all_articles(FolderItem *item) +{ + gchar *dir; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS); + + debug_print("Deleting all cached articles...\n"); + + dir = folder_item_get_path(item); + remove_all_numbered_files(dir); + g_free(dir); +} + +static void news_delete_expired_caches(GSList *alist, FolderItem *item) +{ + gchar *dir; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS); + + debug_print("Deleting expired cached articles...\n"); + + dir = folder_item_get_path(item); + remove_expired_files(dir, 24 * 7); + g_free(dir); +} diff --git a/libsylph/news.h b/libsylph/news.h new file mode 100644 index 0000000..8adcb2f --- /dev/null +++ b/libsylph/news.h @@ -0,0 +1,60 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2007 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __NEWS_H__ +#define __NEWS_H__ + +#include +#include + +#include "folder.h" + +typedef struct _NewsFolder NewsFolder; +typedef struct _NewsGroupInfo NewsGroupInfo; + +#define NEWS_FOLDER(obj) ((NewsFolder *)obj) + +struct _NewsFolder +{ + RemoteFolder rfolder; + + gboolean use_auth; +}; + +struct _NewsGroupInfo +{ + gchar *name; + guint first; + guint last; + gchar type; + gboolean subscribed; +}; + +FolderClass *news_get_class (void); + +GSList *news_get_group_list (Folder *folder); +void news_group_list_free (GSList *group_list); +void news_remove_group_list_cache (Folder *folder); + +gint news_post (Folder *folder, + const gchar *file); +gint news_post_stream (Folder *folder, + FILE *fp); + +#endif /* __NEWS_H__ */ diff --git a/libsylph/nntp.c b/libsylph/nntp.c new file mode 100644 index 0000000..64c9da1 --- /dev/null +++ b/libsylph/nntp.c @@ -0,0 +1,471 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2012 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +#include "nntp.h" +#include "socket.h" +#include "utils.h" +#if USE_SSL +# include "ssl.h" +#endif +#include "socks.h" + +static gint verbose = 1; + +static void nntp_session_destroy(Session *session); + +static gint nntp_ok (SockInfo *sock, + gchar *argbuf); + +static gint nntp_gen_send (SockInfo *sock, + const gchar *format, + ...); +static gint nntp_gen_recv (SockInfo *sock, + gchar *buf, + gint size); +static gint nntp_gen_command (NNTPSession *session, + gchar *argbuf, + const gchar *format, + ...); + + +#if USE_SSL +Session *nntp_session_new_full(const gchar *server, gushort port, + SocksInfo *socks_info, gchar *buf, + const gchar *userid, const gchar *passwd, + SSLType ssl_type) +#else +Session *nntp_session_new_full(const gchar *server, gushort port, + SocksInfo *socks_info, gchar *buf, + const gchar *userid, const gchar *passwd) +#endif +{ + NNTPSession *session; + SockInfo *sock; + const gchar *server_; + gushort port_; + + if (socks_info) { + server_ = socks_info->proxy_host; + port_ = socks_info->proxy_port; + } else { + server_ = server; + port_ = port; + } + + if ((sock = sock_connect(server_, port_)) == NULL) { + log_warning(_("Can't connect to NNTP server: %s:%d\n"), + server, port); + return NULL; + } + + if (socks_info) { + if (socks_connect(sock, server, port, socks_info) < 0) { + log_warning("Can't establish SOCKS connection: %s:%d\n", + server, port); + sock_close(sock); + return NULL; + } + } + +#if USE_SSL + if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) { + log_warning("Can't establish NNTP session with: %s:%d\n", + server, port); + sock_close(sock); + return NULL; + } +#endif + + if (nntp_ok(sock, buf) != NN_SUCCESS) { + sock_close(sock); + return NULL; + } + + session = g_new0(NNTPSession, 1); + + session_init(SESSION(session)); + + SESSION(session)->type = SESSION_NEWS; + SESSION(session)->server = g_strdup(server); + SESSION(session)->sock = sock; + SESSION(session)->last_access_time = time(NULL); + SESSION(session)->data = NULL; + + SESSION(session)->destroy = nntp_session_destroy; + + session->group = NULL; + + if (userid && passwd) { + gint ok; + + session->userid = g_strdup(userid); + session->passwd = g_strdup(passwd); + + ok = nntp_gen_send(sock, "AUTHINFO USER %s", session->userid); + if (ok != NN_SUCCESS) { + session_destroy(SESSION(session)); + return NULL; + } + ok = nntp_ok(sock, NULL); + if (ok == NN_AUTHCONT) { + ok = nntp_gen_send(sock, "AUTHINFO PASS %s", + session->passwd); + if (ok != NN_SUCCESS) { + session_destroy(SESSION(session)); + return NULL; + } + ok = nntp_ok(sock, NULL); + if (ok != NN_SUCCESS) + session->auth_failed = TRUE; + } + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + return NULL; + } + } + + session_set_access_time(SESSION(session)); + + return SESSION(session); +} + +#if USE_SSL +Session *nntp_session_new(const gchar *server, gushort port, gchar *buf, + const gchar *userid, const gchar *passwd, + SSLType ssl_type) +{ + return nntp_session_new_full(server, port, NULL, buf, userid, passwd, + ssl_type); +} +#else +Session *nntp_session_new(const gchar *server, gushort port, gchar *buf, + const gchar *userid, const gchar *passwd) +{ + return nntp_session_new_full(server, port, NULL, buf, userid, passwd); +} +#endif + +static void nntp_session_destroy(Session *session) +{ + NNTPSession *nntp_session = NNTP_SESSION(session); + + g_return_if_fail(session != NULL); + + g_free(nntp_session->group); + g_free(nntp_session->userid); + g_free(nntp_session->passwd); +} + +gint nntp_group(NNTPSession *session, const gchar *group, + gint *num, gint *first, gint *last) +{ + gint ok; + gint resp; + gchar buf[NNTPBUFSIZE]; + + ok = nntp_gen_command(session, buf, "GROUP %s", group); + + if (ok != NN_SUCCESS && ok != NN_SOCKET && ok != NN_AUTHREQ) { + ok = nntp_mode(session, FALSE); + if (ok == NN_SUCCESS) + ok = nntp_gen_command(session, buf, "GROUP %s", group); + } + + if (ok != NN_SUCCESS) + return ok; + + if (sscanf(buf, "%d %d %d %d", &resp, num, first, last) + != 4) { + log_warning(_("protocol error: %s\n"), buf); + return NN_PROTOCOL; + } + + return NN_SUCCESS; +} + +gint nntp_get_article(NNTPSession *session, const gchar *cmd, gint num, + gchar **msgid) +{ + gint ok; + gchar buf[NNTPBUFSIZE]; + + if (num > 0) + ok = nntp_gen_command(session, buf, "%s %d", cmd, num); + else + ok = nntp_gen_command(session, buf, cmd); + + if (ok != NN_SUCCESS) + return ok; + + extract_parenthesis(buf, '<', '>'); + if (buf[0] == '\0') { + log_warning(_("protocol error\n")); + *msgid = g_strdup("0"); + } else + *msgid = g_strdup(buf); + + return NN_SUCCESS; +} + +gint nntp_article(NNTPSession *session, gint num, gchar **msgid) +{ + return nntp_get_article(session, "ARTICLE", num, msgid); +} + +gint nntp_body(NNTPSession *session, gint num, gchar **msgid) +{ + return nntp_get_article(session, "BODY", num, msgid); +} + +gint nntp_head(NNTPSession *session, gint num, gchar **msgid) +{ + return nntp_get_article(session, "HEAD", num, msgid); +} + +gint nntp_stat(NNTPSession *session, gint num, gchar **msgid) +{ + return nntp_get_article(session, "STAT", num, msgid); +} + +gint nntp_next(NNTPSession *session, gint *num, gchar **msgid) +{ + gint ok; + gint resp; + gchar buf[NNTPBUFSIZE]; + + ok = nntp_gen_command(session, buf, "NEXT"); + + if (ok != NN_SUCCESS) + return ok; + + if (sscanf(buf, "%d %d", &resp, num) != 2) { + log_warning(_("protocol error: %s\n"), buf); + return NN_PROTOCOL; + } + + extract_parenthesis(buf, '<', '>'); + if (buf[0] == '\0') { + log_warning(_("protocol error\n")); + return NN_PROTOCOL; + } + *msgid = g_strdup(buf); + + return NN_SUCCESS; +} + +gint nntp_xover(NNTPSession *session, gint first, gint last) +{ + gint ok; + gchar buf[NNTPBUFSIZE]; + + ok = nntp_gen_command(session, buf, "XOVER %d-%d", first, last); + if (ok != NN_SUCCESS) + return ok; + + return NN_SUCCESS; +} + +gint nntp_xhdr(NNTPSession *session, const gchar *header, gint first, gint last) +{ + gint ok; + gchar buf[NNTPBUFSIZE]; + + ok = nntp_gen_command(session, buf, "XHDR %s %d-%d", + header, first, last); + if (ok != NN_SUCCESS) + return ok; + + return NN_SUCCESS; +} + +gint nntp_list(NNTPSession *session) +{ + return nntp_gen_command(session, NULL, "LIST"); +} + +gint nntp_post(NNTPSession *session, FILE *fp) +{ + gint ok; + gchar buf[NNTPBUFSIZE]; + gchar *msg; + + ok = nntp_gen_command(session, buf, "POST"); + if (ok != NN_SUCCESS) + return ok; + + msg = get_outgoing_rfc2822_str(fp); + if (sock_write_all(SESSION(session)->sock, msg, strlen(msg)) < 0) { + log_warning(_("Error occurred while posting\n")); + g_free(msg); + return NN_SOCKET; + } + g_free(msg); + + sock_write_all(SESSION(session)->sock, ".\r\n", 3); + if ((ok = nntp_ok(SESSION(session)->sock, buf)) != NN_SUCCESS) + return ok; + + session_set_access_time(SESSION(session)); + + return NN_SUCCESS; +} + +gint nntp_newgroups(NNTPSession *session) +{ + return NN_SUCCESS; +} + +gint nntp_newnews(NNTPSession *session) +{ + return NN_SUCCESS; +} + +gint nntp_mode(NNTPSession *session, gboolean stream) +{ + gint ok; + + ok = nntp_gen_command(session, NULL, "MODE %s", + stream ? "STREAM" : "READER"); + + return ok; +} + +static gint nntp_ok(SockInfo *sock, gchar *argbuf) +{ + gint ok; + gchar buf[NNTPBUFSIZE]; + + if ((ok = nntp_gen_recv(sock, buf, sizeof(buf))) == NN_SUCCESS) { + if (strlen(buf) < 3) + return NN_ERROR; + + if ((buf[0] == '1' || buf[0] == '2' || buf[0] == '3') && + (buf[3] == ' ' || buf[3] == '\0')) { + if (argbuf) + strcpy(argbuf, buf); + + if (!strncmp(buf, "381", 3)) + return NN_AUTHCONT; + + return NN_SUCCESS; + } else if (!strncmp(buf, "480", 3)) + return NN_AUTHREQ; + else + return NN_ERROR; + } + + return ok; +} + +static gint nntp_gen_send(SockInfo *sock, const gchar *format, ...) +{ + gchar buf[NNTPBUFSIZE]; + va_list args; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + if (verbose) { + if (!g_ascii_strncasecmp(buf, "AUTHINFO PASS", 13)) + log_print("NNTP> AUTHINFO PASS ********\n"); + else + log_print("NNTP> %s\n", buf); + } + + strcat(buf, "\r\n"); + if (sock_write_all(sock, buf, strlen(buf)) < 0) { + log_warning(_("Error occurred while sending command\n")); + return NN_SOCKET; + } + + return NN_SUCCESS; +} + +static gint nntp_gen_recv(SockInfo *sock, gchar *buf, gint size) +{ + if (sock_gets(sock, buf, size) == -1) + return NN_SOCKET; + + strretchomp(buf); + + if (verbose) + log_print("NNTP< %s\n", buf); + + return NN_SUCCESS; +} + +static gint nntp_gen_command(NNTPSession *session, gchar *argbuf, + const gchar *format, ...) +{ + gchar buf[NNTPBUFSIZE]; + va_list args; + gint ok; + SockInfo *sock; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + sock = SESSION(session)->sock; + ok = nntp_gen_send(sock, "%s", buf); + if (ok != NN_SUCCESS) + return ok; + ok = nntp_ok(sock, argbuf); + if (ok == NN_AUTHREQ) { + if (!session->userid || !session->passwd) { + session->auth_failed = TRUE; + return ok; + } + + ok = nntp_gen_send(sock, "AUTHINFO USER %s", session->userid); + if (ok != NN_SUCCESS) + return ok; + ok = nntp_ok(sock, NULL); + if (ok == NN_AUTHCONT) { + ok = nntp_gen_send(sock, "AUTHINFO PASS %s", + session->passwd); + if (ok != NN_SUCCESS) + return ok; + ok = nntp_ok(sock, NULL); + } + if (ok != NN_SUCCESS) { + session->auth_failed = TRUE; + return ok; + } + + ok = nntp_gen_send(sock, "%s", buf); + if (ok != NN_SUCCESS) + return ok; + ok = nntp_ok(sock, argbuf); + } + + session_set_access_time(SESSION(session)); + + return ok; +} diff --git a/libsylph/nntp.h b/libsylph/nntp.h new file mode 100644 index 0000000..045fae7 --- /dev/null +++ b/libsylph/nntp.h @@ -0,0 +1,123 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2012 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __NNTP_H__ +#define __NNTP_H__ + +#include "session.h" +#if USE_SSL +# include "ssl.h" +#endif +#include "socks.h" + +typedef struct _NNTPSession NNTPSession; + +#define NNTP_SESSION(obj) ((NNTPSession *)obj) + +struct _NNTPSession +{ + Session session; + + gchar *group; + + gchar *userid; + gchar *passwd; + gboolean auth_failed; +}; + +#define NN_SUCCESS 0 +#define NN_SOCKET 2 +#define NN_AUTHFAIL 3 +#define NN_PROTOCOL 4 +#define NN_SYNTAX 5 +#define NN_IOERR 6 +#define NN_ERROR 7 +#define NN_AUTHREQ 8 +#define NN_AUTHCONT 9 + +#define NNTPBUFSIZE 8192 + +#if USE_SSL +Session *nntp_session_new (const gchar *server, + gushort port, + gchar *buf, + const gchar *userid, + const gchar *passwd, + SSLType ssl_type); +Session *nntp_session_new_full (const gchar *server, + gushort port, + SocksInfo *socks_info, + gchar *buf, + const gchar *userid, + const gchar *passwd, + SSLType ssl_type); +#else +Session *nntp_session_new (const gchar *server, + gushort port, + gchar *buf, + const gchar *userid, + const gchar *passwd); +Session *nntp_session_new_full (const gchar *server, + gushort port, + SocksInfo *socks_info, + gchar *buf, + const gchar *userid, + const gchar *passwd); +#endif + +gint nntp_group (NNTPSession *session, + const gchar *group, + gint *num, + gint *first, + gint *last); +gint nntp_get_article (NNTPSession *session, + const gchar *cmd, + gint num, + gchar **msgid); +gint nntp_article (NNTPSession *session, + gint num, + gchar **msgid); +gint nntp_body (NNTPSession *session, + gint num, + gchar **msgid); +gint nntp_head (NNTPSession *session, + gint num, + gchar **msgid); +gint nntp_stat (NNTPSession *session, + gint num, + gchar **msgid); +gint nntp_next (NNTPSession *session, + gint *num, + gchar **msgid); +gint nntp_xover (NNTPSession *session, + gint first, + gint last); +gint nntp_xhdr (NNTPSession *session, + const gchar *header, + gint first, + gint last); +gint nntp_list (NNTPSession *session); +gint nntp_post (NNTPSession *session, + FILE *fp); +gint nntp_newgroups (NNTPSession *session); +gint nntp_newnews (NNTPSession *session); +gint nntp_mode (NNTPSession *session, + gboolean stream); + +#endif /* __NNTP_H__ */ diff --git a/libsylph/pop.c b/libsylph/pop.c new file mode 100644 index 0000000..8cb7f5c --- /dev/null +++ b/libsylph/pop.c @@ -0,0 +1,927 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2014 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pop.h" +#include "md5.h" +#include "prefs.h" +#include "prefs_account.h" +#include "utils.h" +#include "recv.h" + +gint pop3_greeting_recv (Pop3Session *session, + const gchar *msg); +gint pop3_getauth_user_send (Pop3Session *session); +gint pop3_getauth_pass_send (Pop3Session *session); +gint pop3_getauth_apop_send (Pop3Session *session); +#if USE_SSL +gint pop3_stls_send (Pop3Session *session); +gint pop3_stls_recv (Pop3Session *session); +#endif +gint pop3_getrange_stat_send (Pop3Session *session); +gint pop3_getrange_stat_recv (Pop3Session *session, + const gchar *msg); +gint pop3_getrange_last_send (Pop3Session *session); +gint pop3_getrange_last_recv (Pop3Session *session, + const gchar *msg); +gint pop3_getrange_uidl_send (Pop3Session *session); +gint pop3_getrange_uidl_recv (Pop3Session *session, + const gchar *data, + guint len); +gint pop3_getsize_list_send (Pop3Session *session); +gint pop3_getsize_list_recv (Pop3Session *session, + const gchar *data, + guint len); +gint pop3_retr_send (Pop3Session *session); +gint pop3_retr_recv (Pop3Session *session, + FILE *fp, + guint len); +gint pop3_delete_send (Pop3Session *session); +gint pop3_delete_recv (Pop3Session *session); +gint pop3_logout_send (Pop3Session *session); + +void pop3_gen_send (Pop3Session *session, + const gchar *format, ...); + +static void pop3_session_destroy (Session *session); + +gint pop3_write_msg_to_file (const gchar *file, + FILE *src_fp, + guint len); + +static Pop3State pop3_lookup_next (Pop3Session *session); + +Pop3ErrorValue pop3_ok (Pop3Session *session, + const gchar *msg); + +static gint pop3_session_recv_msg (Session *session, + const gchar *msg); +static gint pop3_session_recv_data_finished (Session *session, + guchar *data, + guint len); +static gint pop3_session_recv_data_as_file_finished + (Session *session, + FILE *fp, + guint len); + + +gint pop3_greeting_recv(Pop3Session *session, const gchar *msg) +{ + session->state = POP3_GREETING; + + session->greeting = g_strdup(msg); + return PS_SUCCESS; +} + +#if USE_SSL +gint pop3_stls_send(Pop3Session *session) +{ + session->state = POP3_STLS; + pop3_gen_send(session, "STLS"); + return PS_SUCCESS; +} + +gint pop3_stls_recv(Pop3Session *session) +{ + if (session_start_tls(SESSION(session)) < 0) { + session->error_val = PS_SOCKET; + return PS_SOCKET; + } + return PS_SUCCESS; +} +#endif /* USE_SSL */ + +gint pop3_getauth_user_send(Pop3Session *session) +{ + g_return_val_if_fail(session->user != NULL, -1); + + session->state = POP3_GETAUTH_USER; + pop3_gen_send(session, "USER %s", session->user); + return PS_SUCCESS; +} + +gint pop3_getauth_pass_send(Pop3Session *session) +{ + g_return_val_if_fail(session->pass != NULL, -1); + + session->state = POP3_GETAUTH_PASS; + pop3_gen_send(session, "PASS %s", session->pass); + return PS_SUCCESS; +} + +gint pop3_getauth_apop_send(Pop3Session *session) +{ + gchar *start, *end; + gchar *apop_str; + SMD5 *md5; + gchar *md5sum; + + g_return_val_if_fail(session->user != NULL, -1); + g_return_val_if_fail(session->pass != NULL, -1); + + session->state = POP3_GETAUTH_APOP; + + if ((start = strchr(session->greeting, '<')) == NULL) { + log_warning(_("Required APOP timestamp not found " + "in greeting\n")); + session->error_val = PS_PROTOCOL; + return PS_PROTOCOL; + } + + if ((end = strchr(start, '>')) == NULL || end == start + 1) { + log_warning(_("Timestamp syntax error in greeting\n")); + session->error_val = PS_PROTOCOL; + return PS_PROTOCOL; + } + + *(end + 1) = '\0'; + + if (!is_ascii_str(start) || strchr(start, '@') == NULL) { + log_warning(_("Invalid timestamp in greeting\n")); + session->error_val = PS_PROTOCOL; + return PS_PROTOCOL; + } + + apop_str = g_strconcat(start, session->pass, NULL); + md5 = s_gnet_md5_new((guchar *)apop_str, strlen(apop_str)); + md5sum = s_gnet_md5_get_string(md5); + + pop3_gen_send(session, "APOP %s %s", session->user, md5sum); + + g_free(md5sum); + s_gnet_md5_delete(md5); + g_free(apop_str); + + return PS_SUCCESS; +} + +gint pop3_getrange_stat_send(Pop3Session *session) +{ + session->state = POP3_GETRANGE_STAT; + pop3_gen_send(session, "STAT"); + return PS_SUCCESS; +} + +gint pop3_getrange_stat_recv(Pop3Session *session, const gchar *msg) +{ + if (sscanf(msg, "%d %lld", &session->count, &session->total_bytes) != 2) { + log_warning(_("POP3 protocol error\n")); + session->error_val = PS_PROTOCOL; + return PS_PROTOCOL; + } else { + if (session->count == 0) { + session->uidl_is_valid = TRUE; + } else { + session->msg = g_new0(Pop3MsgInfo, session->count + 1); + session->cur_msg = 1; + } + } + + return PS_SUCCESS; +} + +gint pop3_getrange_last_send(Pop3Session *session) +{ + session->state = POP3_GETRANGE_LAST; + pop3_gen_send(session, "LAST"); + return PS_SUCCESS; +} + +gint pop3_getrange_last_recv(Pop3Session *session, const gchar *msg) +{ + gint last; + + if (sscanf(msg, "%d", &last) == 0) { + log_warning(_("POP3 protocol error\n")); + session->error_val = PS_PROTOCOL; + return PS_PROTOCOL; + } else { + if (session->count > last) { + session->new_msg_exist = TRUE; + session->cur_msg = last + 1; + } else + session->cur_msg = 0; + } + + return PS_SUCCESS; +} + +gint pop3_getrange_uidl_send(Pop3Session *session) +{ + session->state = POP3_GETRANGE_UIDL; + pop3_gen_send(session, "UIDL"); + return PS_SUCCESS; +} + +gint pop3_getrange_uidl_recv(Pop3Session *session, const gchar *data, guint len) +{ + gchar id[IDLEN + 1]; + gchar buf[POPBUFSIZE]; + gint buf_len; + gint num; + time_t recv_time; + const gchar *p = data; + const gchar *lastp = data + len; + const gchar *newline; + + while (p < lastp) { + if ((newline = memchr(p, '\r', lastp - p)) == NULL) + return PS_PROTOCOL; + buf_len = MIN(newline - p, sizeof(buf) - 1); + memcpy(buf, p, buf_len); + buf[buf_len] = '\0'; + + p = newline + 1; + if (p < lastp && *p == '\n') p++; + + if (sscanf(buf, "%d %" Xstr(IDLEN) "s", &num, id) != 2 || + num <= 0 || num > session->count) { + log_warning(_("invalid UIDL response: %s\n"), buf); + continue; + } + + session->msg[num].uidl = g_strdup(id); + + recv_time = GPOINTER_TO_INT(g_hash_table_lookup(session->uidl_table, id)); + session->msg[num].recv_time = recv_time; + + if (!session->ac_prefs->getall && recv_time != RECV_TIME_NONE) + session->msg[num].received = TRUE; + + if (!session->new_msg_exist && + (session->ac_prefs->getall || recv_time == RECV_TIME_NONE || + session->ac_prefs->rmmail)) { + session->cur_msg = num; + session->new_msg_exist = TRUE; + } + } + + session->uidl_is_valid = TRUE; + return PS_SUCCESS; +} + +gint pop3_getsize_list_send(Pop3Session *session) +{ + session->state = POP3_GETSIZE_LIST; + pop3_gen_send(session, "LIST"); + return PS_SUCCESS; +} + +gint pop3_getsize_list_recv(Pop3Session *session, const gchar *data, guint len) +{ + gchar buf[POPBUFSIZE]; + gint buf_len; + guint num, size; + const gchar *p = data; + const gchar *lastp = data + len; + const gchar *newline; + + while (p < lastp) { + if ((newline = memchr(p, '\r', lastp - p)) == NULL) + return PS_PROTOCOL; + buf_len = MIN(newline - p, sizeof(buf) - 1); + memcpy(buf, p, buf_len); + buf[buf_len] = '\0'; + + p = newline + 1; + if (p < lastp && *p == '\n') p++; + + if (sscanf(buf, "%u %u", &num, &size) != 2) { + session->error_val = PS_PROTOCOL; + return PS_PROTOCOL; + } + + if (num > 0 && num <= session->count) + session->msg[num].size = size; + if (num > 0 && num < session->cur_msg) + session->cur_total_bytes += size; + } + + return PS_SUCCESS; +} + +gint pop3_retr_send(Pop3Session *session) +{ + session->state = POP3_RETR; + pop3_gen_send(session, "RETR %d", session->cur_msg); + return PS_SUCCESS; +} + +gint pop3_retr_recv(Pop3Session *session, FILE *fp, guint len) +{ + gchar *file; + gint drop_ok; + + file = get_tmp_file(); + if (pop3_write_msg_to_file(file, fp, len) < 0) { + g_free(file); + session->error_val = PS_IOERR; + return PS_IOERR; + } + + drop_ok = session->drop_message(session, file); + g_unlink(file); + g_free(file); + if (drop_ok < 0) { + session->error_val = PS_IOERR; + return PS_IOERR; + } + + session->cur_total_bytes += session->msg[session->cur_msg].size; + session->cur_total_recv_bytes += session->msg[session->cur_msg].size; + session->cur_total_num++; + + session->msg[session->cur_msg].received = TRUE; + session->msg[session->cur_msg].recv_time = + drop_ok == DROP_DONT_RECEIVE ? RECV_TIME_KEEP + : drop_ok == DROP_DELETE ? RECV_TIME_DELETE + : session->current_time; + + return PS_SUCCESS; +} + +gint pop3_delete_send(Pop3Session *session) +{ + session->state = POP3_DELETE; + pop3_gen_send(session, "DELE %d", session->cur_msg); + return PS_SUCCESS; +} + +gint pop3_delete_recv(Pop3Session *session) +{ + session->msg[session->cur_msg].recv_time = RECV_TIME_DELETE; + session->msg[session->cur_msg].deleted = TRUE; + return PS_SUCCESS; +} + +gint pop3_logout_send(Pop3Session *session) +{ + session->state = POP3_LOGOUT; + pop3_gen_send(session, "QUIT"); + return PS_SUCCESS; +} + +void pop3_gen_send(Pop3Session *session, const gchar *format, ...) +{ + gchar buf[POPBUFSIZE + 1]; + va_list args; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf) - 2, format, args); + va_end(args); + + if (!g_ascii_strncasecmp(buf, "PASS ", 5)) + log_print("POP3> PASS ********\n"); + else + log_print("POP3> %s\n", buf); + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf); +} + +Session *pop3_session_new(PrefsAccount *account) +{ + Pop3Session *session; + + g_return_val_if_fail(account != NULL, NULL); + + session = g_new0(Pop3Session, 1); + + session_init(SESSION(session)); + + SESSION(session)->type = SESSION_POP3; + + SESSION(session)->recv_msg = pop3_session_recv_msg; + SESSION(session)->send_data_finished = NULL; + SESSION(session)->recv_data_finished = pop3_session_recv_data_finished; + SESSION(session)->recv_data_as_file_finished = + pop3_session_recv_data_as_file_finished; + + SESSION(session)->destroy = pop3_session_destroy; + + session->state = POP3_READY; + session->ac_prefs = account; + session->uidl_table = pop3_get_uidl_table(account); + session->current_time = time(NULL); + session->error_val = PS_SUCCESS; + session->error_msg = NULL; + + session->user = g_strdup(account->userid); + session->pass = account->passwd ? g_strdup(account->passwd) : + account->tmp_pass ? g_strdup(account->tmp_pass) : NULL; + + SESSION(session)->server = g_strdup(account->recv_server); + +#if USE_SSL + SESSION(session)->port = account->set_popport ? + account->popport : account->ssl_pop == SSL_TUNNEL ? 995 : 110; + SESSION(session)->ssl_type = account->ssl_pop; + if (account->ssl_pop != SSL_NONE) + SESSION(session)->nonblocking = account->use_nonblocking_ssl; +#else + SESSION(session)->port = account->set_popport ? account->popport : 110; +#endif + + return SESSION(session); +} + +static void pop3_session_destroy(Session *session) +{ + Pop3Session *pop3_session = POP3_SESSION(session); + gint n; + + g_return_if_fail(session != NULL); + + for (n = 1; n <= pop3_session->count; n++) + g_free(pop3_session->msg[n].uidl); + g_free(pop3_session->msg); + + if (pop3_session->uidl_table) { + hash_free_strings(pop3_session->uidl_table); + g_hash_table_destroy(pop3_session->uidl_table); + } + + g_free(pop3_session->greeting); + g_free(pop3_session->user); + g_free(pop3_session->pass); + g_free(pop3_session->error_msg); +} + +GHashTable *pop3_get_uidl_table(PrefsAccount *ac_prefs) +{ + GHashTable *table; + gchar *path; + FILE *fp; + gchar buf[POPBUFSIZE]; + gchar uidl[POPBUFSIZE]; + time_t recv_time; + time_t now; + gchar *uid; + + table = g_hash_table_new(g_str_hash, g_str_equal); + + uid = uriencode_for_filename(ac_prefs->userid); + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + UIDL_DIR, G_DIR_SEPARATOR_S, ac_prefs->recv_server, + "-", uid, NULL); + g_free(uid); + if ((fp = g_fopen(path, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(path, "fopen"); + g_free(path); + return table; + } + g_free(path); + + now = time(NULL); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + recv_time = RECV_TIME_NONE; + if (sscanf(buf, "%s\t%ld", uidl, &recv_time) != 2) { + if (sscanf(buf, "%s", uidl) != 1) + continue; + else + recv_time = now; + } + if (recv_time == RECV_TIME_NONE) + recv_time = RECV_TIME_RECEIVED; + g_hash_table_insert(table, g_strdup(uidl), + GINT_TO_POINTER(recv_time)); + } + + fclose(fp); + return table; +} + +gint pop3_write_uidl_list(Pop3Session *session) +{ + gchar *path; + PrefFile *pfile; + Pop3MsgInfo *msg; + gint n; + gchar *uid; + + if (!session->uidl_is_valid) return 0; + + uid = uriencode_for_filename(session->ac_prefs->userid); + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + UIDL_DIR, G_DIR_SEPARATOR_S, + session->ac_prefs->recv_server, + "-", uid, NULL); + g_free(uid); + if ((pfile = prefs_file_open(path)) == NULL) { + g_free(path); + return -1; + } + prefs_file_set_backup_generation(pfile, 0); + + for (n = 1; n <= session->count; n++) { + msg = &session->msg[n]; + if (!msg->uidl || !msg->received) + continue; + if (session->state == POP3_DONE && msg->deleted) + continue; + fprintf(pfile->fp, "%s\t%ld\n", msg->uidl, msg->recv_time); + } + + if (prefs_file_close(pfile) < 0) + g_warning("%s: failed to write UIDL list.\n", path); + + g_free(path); + + return 0; +} + +gint pop3_write_msg_to_file(const gchar *file, FILE *src_fp, guint len) +{ + FILE *fp; + gchar buf[BUFFSIZE]; + gchar last_ch = '\0'; + + g_return_val_if_fail(file != NULL, -1); + + if ((fp = g_fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + if (change_file_mode_rw(fp, file) < 0) + FILE_OP_ERROR(file, "chmod"); + + while (fgets(buf, sizeof(buf), src_fp) != NULL) { + gchar *p = buf; + gint len; + + len = strlen(buf); + if (len > 0) { + last_ch = buf[len - 1]; + if (last_ch == '\n' && len > 1 && + buf[len - 2] == '\r') { + buf[len - 2] = '\n'; + buf[len - 1] = '\0'; + } else if (last_ch == '\r') + buf[len - 1] = '\0'; + } else + last_ch = '\0'; + + if ((last_ch == '\0' || last_ch == '\n') && + *p == '.' && *(p + 1) == '.') + p++; + + if (fputs(p, fp) == EOF) { + FILE_OP_ERROR(file, "fputs"); + g_warning("can't write to file: %s\n", file); + fclose(fp); + g_unlink(file); + return -1; + } + } + + if (ferror(src_fp)) { + FILE_OP_ERROR(file, "fgets"); + fclose(fp); + g_unlink(file); + return -1; + } + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(file, "fclose"); + g_unlink(file); + return -1; + } + + return 0; +} + +static Pop3State pop3_lookup_next(Pop3Session *session) +{ + Pop3MsgInfo *msg; + PrefsAccount *ac = session->ac_prefs; + gint size; + gboolean size_limit_over; + + for (;;) { + msg = &session->msg[session->cur_msg]; + size = msg->size; + size_limit_over = + (ac->enable_size_limit && + ac->size_limit > 0 && + size > ac->size_limit * 1024); + + if (msg->recv_time == RECV_TIME_DELETE || + (ac->rmmail && + msg->recv_time != RECV_TIME_NONE && + msg->recv_time != RECV_TIME_KEEP && + session->current_time - msg->recv_time >= + ac->msg_leave_time * 24 * 60 * 60)) { + log_print(_("POP3: Deleting expired message %d\n"), + session->cur_msg); + session->cur_total_bytes += size; + pop3_delete_send(session); + return POP3_DELETE; + } + + if (size_limit_over && !msg->received) { + log_print + (_("POP3: Skipping message %d (%d bytes)\n"), + session->cur_msg, size); + session->skipped_num++; + } + + if (size == 0 || msg->received || size_limit_over) { + session->cur_total_bytes += size; + if (session->cur_msg == session->count) { + pop3_logout_send(session); + return POP3_LOGOUT; + } else + session->cur_msg++; + } else + break; + } + + pop3_retr_send(session); + + return POP3_RETR; +} + +Pop3ErrorValue pop3_ok(Pop3Session *session, const gchar *msg) +{ + Pop3ErrorValue ok; + + log_print("POP3< %s\n", msg); + + if (!strncmp(msg, "+OK", 3)) + ok = PS_SUCCESS; + else if (!strncmp(msg, "-ERR", 4)) { + if (strstr(msg + 4, "lock") || + strstr(msg + 4, "Lock") || + strstr(msg + 4, "LOCK") || + strstr(msg + 4, "wait")) { + log_warning(_("mailbox is locked\n")); + ok = PS_LOCKBUSY; + } else if (strcasestr(msg + 4, "timeout")) { + log_warning(_("session timeout\n")); + ok = PS_ERROR; + } else { + switch (session->state) { +#if USE_SSL + case POP3_STLS: + log_warning(_("can't start TLS session\n")); + ok = PS_ERROR; + break; +#endif + case POP3_GETAUTH_USER: + case POP3_GETAUTH_PASS: + case POP3_GETAUTH_APOP: + log_warning(_("error occurred on authentication\n")); + ok = PS_AUTHFAIL; + break; + case POP3_GETRANGE_LAST: + case POP3_GETRANGE_UIDL: + log_warning(_("command not supported\n")); + ok = PS_NOTSUPPORTED; + break; + default: + log_warning(_("error occurred on POP3 session\n")); + ok = PS_ERROR; + } + } + + g_free(session->error_msg); + session->error_msg = g_strdup(msg); + fprintf(stderr, "POP3: %s\n", msg); + } else + ok = PS_PROTOCOL; + + /* don't overwrite previous error on logout */ + if (session->state != POP3_LOGOUT) + session->error_val = ok; + + return ok; +} + +static gint pop3_session_recv_msg(Session *session, const gchar *msg) +{ + Pop3Session *pop3_session = POP3_SESSION(session); + gint val = PS_SUCCESS; + const gchar *body; + + body = msg; + if (pop3_session->state != POP3_GETRANGE_UIDL_RECV && + pop3_session->state != POP3_GETSIZE_LIST_RECV) { + val = pop3_ok(pop3_session, msg); + if (val != PS_SUCCESS) { + if (val == PS_SOCKET) { + pop3_session->state = POP3_ERROR; + return -1; + } + if (val != PS_NOTSUPPORTED) { + if (pop3_session->state != POP3_LOGOUT) { + if (pop3_logout_send(pop3_session) == PS_SUCCESS) + return 0; + else + return -1; + } + } + } + + if (*body == '+' || *body == '-') + body++; + while (g_ascii_isalpha(*body)) + body++; + while (g_ascii_isspace(*body)) + body++; + } + + switch (pop3_session->state) { + case POP3_READY: + case POP3_GREETING: + val = pop3_greeting_recv(pop3_session, body); +#if USE_SSL + if (pop3_session->ac_prefs->ssl_pop == SSL_STARTTLS) + val = pop3_stls_send(pop3_session); + else +#endif + if (pop3_session->ac_prefs->use_apop_auth) + val = pop3_getauth_apop_send(pop3_session); + else + val = pop3_getauth_user_send(pop3_session); + break; +#if USE_SSL + case POP3_STLS: + if ((val = pop3_stls_recv(pop3_session)) != PS_SUCCESS) + return -1; + if (pop3_session->ac_prefs->use_apop_auth) + val = pop3_getauth_apop_send(pop3_session); + else + val = pop3_getauth_user_send(pop3_session); + break; +#endif + case POP3_GETAUTH_USER: + val = pop3_getauth_pass_send(pop3_session); + break; + case POP3_GETAUTH_PASS: + case POP3_GETAUTH_APOP: + if (pop3_session->auth_only) + val = pop3_logout_send(pop3_session); + else + val = pop3_getrange_stat_send(pop3_session); + break; + case POP3_GETRANGE_STAT: + if ((val = pop3_getrange_stat_recv(pop3_session, body)) != PS_SUCCESS) + return -1; + if (pop3_session->count > 0) + val = pop3_getrange_uidl_send(pop3_session); + else + val = pop3_logout_send(pop3_session); + break; + case POP3_GETRANGE_LAST: + if (val == PS_NOTSUPPORTED) + pop3_session->error_val = PS_SUCCESS; + else if ((val = pop3_getrange_last_recv + (pop3_session, body)) != PS_SUCCESS) + return -1; + if (pop3_session->cur_msg > 0) + val = pop3_getsize_list_send(pop3_session); + else + val = pop3_logout_send(pop3_session); + break; + case POP3_GETRANGE_UIDL: + if (val == PS_NOTSUPPORTED) { + pop3_session->error_val = PS_SUCCESS; + val = pop3_getrange_last_send(pop3_session); + } else { + pop3_session->state = POP3_GETRANGE_UIDL_RECV; + val = session_recv_data(session, 0, ".\r\n"); + } + break; + case POP3_GETSIZE_LIST: + pop3_session->state = POP3_GETSIZE_LIST_RECV; + val = session_recv_data(session, 0, ".\r\n"); + break; + case POP3_RETR: + pop3_session->state = POP3_RETR_RECV; + val = session_recv_data_as_file(session, 0, ".\r\n"); + break; + case POP3_DELETE: + val = pop3_delete_recv(pop3_session); + if (pop3_session->cur_msg == pop3_session->count) + val = pop3_logout_send(pop3_session); + else { + pop3_session->cur_msg++; + if (pop3_lookup_next(pop3_session) == POP3_ERROR) + return -1; + } + break; + case POP3_LOGOUT: + if (val == PS_SUCCESS) + pop3_session->state = POP3_DONE; + else + pop3_session->state = POP3_ERROR; + session_disconnect(session); + break; + case POP3_ERROR: + default: + return -1; + } + + if (val == PS_SUCCESS) + return 0; + else + return -1; +} + +static gint pop3_session_recv_data_finished(Session *session, guchar *data, + guint len) +{ + Pop3Session *pop3_session = POP3_SESSION(session); + Pop3ErrorValue val = PS_SUCCESS; + + switch (pop3_session->state) { + case POP3_GETRANGE_UIDL_RECV: + val = pop3_getrange_uidl_recv(pop3_session, (gchar *)data, len); + if (val == PS_SUCCESS) { + if (pop3_session->new_msg_exist) + pop3_getsize_list_send(pop3_session); + else + pop3_logout_send(pop3_session); + } else + return -1; + break; + case POP3_GETSIZE_LIST_RECV: + val = pop3_getsize_list_recv(pop3_session, (gchar *)data, len); + if (val == PS_SUCCESS) { + if (pop3_lookup_next(pop3_session) == POP3_ERROR) + return -1; + } else + return -1; + break; + case POP3_ERROR: + default: + return -1; + } + + return 0; +} + +static gint pop3_session_recv_data_as_file_finished(Session *session, FILE *fp, + guint len) +{ + Pop3Session *pop3_session = POP3_SESSION(session); + + g_return_val_if_fail(pop3_session->state == POP3_RETR_RECV, -1); + + if (pop3_retr_recv(pop3_session, fp, len) != PS_SUCCESS) + return -1; + + /* disconnected? */ + if (!session->sock) + return -1; + + if (pop3_session->msg[pop3_session->cur_msg].recv_time + == RECV_TIME_DELETE || + (pop3_session->ac_prefs->rmmail && + pop3_session->ac_prefs->msg_leave_time == 0 && + pop3_session->msg[pop3_session->cur_msg].recv_time + != RECV_TIME_KEEP)) + pop3_delete_send(pop3_session); + else if (pop3_session->cur_msg == pop3_session->count) + pop3_logout_send(pop3_session); + else { + pop3_session->cur_msg++; + if (pop3_lookup_next(pop3_session) == POP3_ERROR) + return -1; + } + + return 0; +} diff --git a/libsylph/pop.h b/libsylph/pop.h new file mode 100644 index 0000000..050caf5 --- /dev/null +++ b/libsylph/pop.h @@ -0,0 +1,157 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2006 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __POP_H__ +#define __POP_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "session.h" +#include "prefs_account.h" +#include "utils.h" + +typedef struct _Pop3MsgInfo Pop3MsgInfo; +typedef struct _Pop3Session Pop3Session; + +#define POP3_SESSION(obj) ((Pop3Session *)obj) + +typedef enum { + POP3_READY, + POP3_GREETING, + POP3_STLS, + POP3_GETAUTH_USER, + POP3_GETAUTH_PASS, + POP3_GETAUTH_APOP, + POP3_GETRANGE_STAT, + POP3_GETRANGE_LAST, + POP3_GETRANGE_UIDL, + POP3_GETRANGE_UIDL_RECV, + POP3_GETSIZE_LIST, + POP3_GETSIZE_LIST_RECV, + POP3_RETR, + POP3_RETR_RECV, + POP3_DELETE, + POP3_LOGOUT, + POP3_DONE, + POP3_ERROR, + + N_POP3_STATE +} Pop3State; + +typedef enum { + PS_SUCCESS = 0, /* command successful */ + PS_NOMAIL = 1, /* no mail available */ + PS_SOCKET = 2, /* socket I/O woes */ + PS_AUTHFAIL = 3, /* user authorization failed */ + PS_PROTOCOL = 4, /* protocol violation */ + PS_SYNTAX = 5, /* command-line syntax error */ + PS_IOERR = 6, /* file I/O error */ + PS_ERROR = 7, /* protocol error */ + PS_EXCLUDE = 8, /* client-side exclusion error */ + PS_LOCKBUSY = 9, /* server responded lock busy */ + PS_SMTP = 10, /* SMTP error */ + PS_DNS = 11, /* fatal DNS error */ + PS_BSMTP = 12, /* output batch could not be opened */ + PS_MAXFETCH = 13, /* poll ended by fetch limit */ + + PS_NOTSUPPORTED = 14, /* command not supported */ + + /* leave space for more codes */ + + PS_CONTINUE = 128 /* more responses may follow */ +} Pop3ErrorValue; + +typedef enum { + RECV_TIME_NONE = 0, + RECV_TIME_RECEIVED = 1, + RECV_TIME_KEEP = 2, + RECV_TIME_DELETE = 3 +} RecvTime; + +typedef enum { + DROP_OK = 0, + DROP_DONT_RECEIVE = 1, + DROP_DELETE = 2, + DROP_ERROR = -1 +} Pop3DropValue; + +struct _Pop3MsgInfo +{ + gint size; + gchar *uidl; + stime_t recv_time; + guint received : 1; + guint deleted : 1; +}; + +struct _Pop3Session +{ + Session session; + + Pop3State state; + + PrefsAccount *ac_prefs; + + gchar *greeting; + gchar *user; + gchar *pass; + gint count; + gint64 total_bytes; + gint cur_msg; + gint cur_total_num; + gint64 cur_total_bytes; + gint64 cur_total_recv_bytes; + gint skipped_num; + + Pop3MsgInfo *msg; + + GHashTable *uidl_table; + + gboolean auth_only; + + gboolean new_msg_exist; + gboolean uidl_is_valid; + + stime_t current_time; + + Pop3ErrorValue error_val; + gchar *error_msg; + + gpointer data; + + /* virtual method to drop message */ + gint (*drop_message) (Pop3Session *session, + const gchar *file); +}; + +#define POPBUFSIZE 512 +/* #define IDLEN 128 */ +#define IDLEN POPBUFSIZE + +Session *pop3_session_new (PrefsAccount *account); + +GHashTable *pop3_get_uidl_table (PrefsAccount *account); +gint pop3_write_uidl_list (Pop3Session *session); + +#endif /* __POP_H__ */ diff --git a/libsylph/prefs.c b/libsylph/prefs.c new file mode 100644 index 0000000..12dd057 --- /dev/null +++ b/libsylph/prefs.c @@ -0,0 +1,588 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2009 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef G_OS_WIN32 +# include +# include +#endif + +#include "prefs.h" +#include "codeconv.h" +#include "utils.h" + +typedef enum +{ + DUMMY_PARAM +} DummyEnum; + +typedef struct _PrefFilePrivate PrefFilePrivate; + +struct _PrefFilePrivate { + FILE *fp; + gchar *path; + gint backup_generation; +}; + +static void prefs_config_parse_one_line (GHashTable *param_table, + const gchar *buf); + +GHashTable *prefs_param_table_get(PrefParam *param) +{ + GHashTable *table; + gint i; + + g_return_val_if_fail(param != NULL, NULL); + + table = g_hash_table_new(g_str_hash, g_str_equal); + + for (i = 0; param[i].name != NULL; i++) { + g_hash_table_insert(table, param[i].name, ¶m[i]); + } + + return table; +} + +void prefs_param_table_destroy(GHashTable *param_table) +{ + g_hash_table_destroy(param_table); +} + +void prefs_read_config(PrefParam *param, const gchar *label, + const gchar *rcfile, const gchar *encoding) +{ + FILE *fp; + gchar buf[PREFSBUFSIZE]; + gchar *block_label; + GHashTable *param_table; + + g_return_if_fail(param != NULL); + g_return_if_fail(label != NULL); + g_return_if_fail(rcfile != NULL); + + debug_print("Reading configuration...\n"); + + prefs_set_default(param); + + if ((fp = g_fopen(rcfile, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcfile, "fopen"); + return; + } + + block_label = g_strdup_printf("[%s]", label); + + /* search aiming block */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + gint val; + + if (encoding) { + gchar *conv_str; + + conv_str = conv_codeset_strdup + (buf, encoding, CS_INTERNAL); + if (!conv_str) + conv_str = g_strdup(buf); + val = strncmp + (conv_str, block_label, strlen(block_label)); + g_free(conv_str); + } else + val = strncmp(buf, block_label, strlen(block_label)); + if (val == 0) { + debug_print("Found %s\n", block_label); + break; + } + } + g_free(block_label); + + param_table = prefs_param_table_get(param); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + if (buf[0] == '\0') continue; + /* reached next block */ + if (buf[0] == '[') break; + + if (encoding) { + gchar *conv_str; + + conv_str = conv_codeset_strdup + (buf, encoding, CS_INTERNAL); + if (!conv_str) + conv_str = g_strdup(buf); + prefs_config_parse_one_line(param_table, conv_str); + g_free(conv_str); + } else + prefs_config_parse_one_line(param_table, buf); + } + + prefs_param_table_destroy(param_table); + + debug_print("Finished reading configuration.\n"); + fclose(fp); +} + +static void prefs_config_parse_one_line(GHashTable *param_table, + const gchar *buf) +{ + PrefParam *param; + const gchar *p = buf; + gchar *name; + const gchar *value; + + while (*p && *p != '=') + p++; + + if (*p != '=') { + g_warning("invalid pref line: %s\n", buf); + return; + } + + name = g_strndup(buf, p - buf); + value = p + 1; + + /* debug_print("%s = %s\n", name, value); */ + + param = g_hash_table_lookup(param_table, name); + + if (!param) { + debug_print("pref key '%s' (value '%s') not found\n", + name, value); + g_free(name); + return; + } + + switch (param->type) { + case P_STRING: + g_free(*((gchar **)param->data)); + *((gchar **)param->data) = *value ? g_strdup(value) : NULL; + break; + case P_INT: + *((gint *)param->data) = (gint)atoi(value); + break; + case P_BOOL: + *((gboolean *)param->data) = + (*value == '0' || *value == '\0') ? FALSE : TRUE; + break; + case P_ENUM: + *((DummyEnum *)param->data) = (DummyEnum)atoi(value); + break; + case P_USHORT: + *((gushort *)param->data) = (gushort)atoi(value); + break; + default: + break; + } + + g_free(name); +} + +#define TRY(func) \ +if (!(func)) \ +{ \ + g_warning(_("failed to write configuration to file\n")); \ + if (orig_fp) fclose(orig_fp); \ + prefs_file_close_revert(pfile); \ + g_free(rcpath); \ + g_free(block_label); \ + return; \ +} \ + +void prefs_write_config(PrefParam *param, const gchar *label, + const gchar *rcfile) +{ + FILE *orig_fp; + PrefFile *pfile; + gchar *rcpath; + gchar buf[PREFSBUFSIZE]; + gchar *block_label = NULL; + gboolean block_matched = FALSE; + + g_return_if_fail(param != NULL); + g_return_if_fail(label != NULL); + g_return_if_fail(rcfile != NULL); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, rcfile, NULL); + if ((orig_fp = g_fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + } + + if ((pfile = prefs_file_open(rcpath)) == NULL) { + g_warning(_("failed to write configuration to file\n")); + if (orig_fp) fclose(orig_fp); + g_free(rcpath); + return; + } + + block_label = g_strdup_printf("[%s]", label); + + /* search aiming block */ + if (orig_fp) { + while (fgets(buf, sizeof(buf), orig_fp) != NULL) { + gint val; + + val = strncmp(buf, block_label, strlen(block_label)); + if (val == 0) { + debug_print(_("Found %s\n"), block_label); + block_matched = TRUE; + break; + } else + TRY(fputs(buf, pfile->fp) != EOF); + } + } + + TRY(fprintf(pfile->fp, "%s\n", block_label) > 0); + g_free(block_label); + block_label = NULL; + + /* write all param data to file */ + TRY(prefs_file_write_param(pfile, param) == 0); + + if (block_matched) { + while (fgets(buf, sizeof(buf), orig_fp) != NULL) { + /* next block */ + if (buf[0] == '[') { + TRY(fputc('\n', pfile->fp) != EOF && + fputs(buf, pfile->fp) != EOF); + break; + } + } + while (fgets(buf, sizeof(buf), orig_fp) != NULL) + TRY(fputs(buf, pfile->fp) != EOF); + } + + if (orig_fp) fclose(orig_fp); + if (prefs_file_close(pfile) < 0) + g_warning(_("failed to write configuration to file\n")); + g_free(rcpath); + + debug_print(_("Configuration is saved.\n")); +} + +gint prefs_file_write_param(PrefFile *pfile, PrefParam *param) +{ + gint i; + gchar buf[PREFSBUFSIZE]; + + for (i = 0; param[i].name != NULL; i++) { + switch (param[i].type) { + case P_STRING: + g_snprintf(buf, sizeof(buf), "%s=%s\n", param[i].name, + *((gchar **)param[i].data) ? + *((gchar **)param[i].data) : ""); + break; + case P_INT: + g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name, + *((gint *)param[i].data)); + break; + case P_BOOL: + g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name, + *((gboolean *)param[i].data)); + break; + case P_ENUM: + g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name, + *((DummyEnum *)param[i].data)); + break; + case P_USHORT: + g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name, + *((gushort *)param[i].data)); + break; + default: + buf[0] = '\0'; + } + + if (buf[0] != '\0') { + if (fputs(buf, pfile->fp) == EOF) { + perror("fputs"); + return -1; + } + } + } + + return 0; +} + +PrefFile *prefs_file_open(const gchar *path) +{ + PrefFilePrivate *pfile; + gchar *tmppath; + FILE *fp; + + g_return_val_if_fail(path != NULL, NULL); + + tmppath = g_strconcat(path, ".tmp", NULL); + if ((fp = g_fopen(tmppath, "wb")) == NULL) { + FILE_OP_ERROR(tmppath, "fopen"); + g_free(tmppath); + return NULL; + } + + if (change_file_mode_rw(fp, tmppath) < 0) + FILE_OP_ERROR(tmppath, "chmod"); + + g_free(tmppath); + + pfile = g_new(PrefFilePrivate, 1); + pfile->fp = fp; + pfile->path = g_strdup(path); + pfile->backup_generation = 4; + + return (PrefFile *)pfile; +} + +void prefs_file_set_backup_generation(PrefFile *pfile, gint generation) +{ + PrefFilePrivate *priv = (PrefFilePrivate *)pfile; + + g_return_if_fail(pfile != NULL); + + priv->backup_generation = generation; +} + +gint prefs_file_get_backup_generation(PrefFile *pfile) +{ + PrefFilePrivate *priv = (PrefFilePrivate *)pfile; + + g_return_val_if_fail(pfile != NULL, -1); + + return priv->backup_generation; +} + +gint prefs_file_close(PrefFile *pfile) +{ + PrefFilePrivate *priv = (PrefFilePrivate *)pfile; + FILE *fp; + gchar *path; + gchar *tmppath; + gchar *bakpath = NULL; + gint generation; + gint ret = 0; + + g_return_val_if_fail(pfile != NULL, -1); + + fp = pfile->fp; + path = pfile->path; + generation = priv->backup_generation; + g_free(pfile); + + tmppath = g_strconcat(path, ".tmp", NULL); + if (fflush(fp) == EOF) { + FILE_OP_ERROR(tmppath, "fflush"); + fclose(fp); + ret = -1; + goto finish; + } +#if HAVE_FSYNC + if (fsync(fileno(fp)) < 0) { + FILE_OP_ERROR(tmppath, "fsync"); + fclose(fp); + ret = -1; + goto finish; + } +#elif defined(G_OS_WIN32) + if (_commit(_fileno(fp)) < 0) { + FILE_OP_ERROR(tmppath, "_commit"); + fclose(fp); + ret = -1; + goto finish; + } +#endif + if (fclose(fp) == EOF) { + FILE_OP_ERROR(tmppath, "fclose"); + ret = -1; + goto finish; + } + + if (is_file_exist(path)) { + bakpath = g_strconcat(path, ".bak", NULL); + if (is_file_exist(bakpath)) { + gint i; + gchar *bakpath_n, *bakpath_p; + + for (i = generation; i > 0; i--) { + bakpath_n = g_strdup_printf("%s.%d", bakpath, + i); + if (i == 1) + bakpath_p = g_strdup(bakpath); + else + bakpath_p = g_strdup_printf + ("%s.%d", bakpath, i - 1); + if (is_file_exist(bakpath_p)) { + if (rename_force(bakpath_p, bakpath_n) < 0) { + FILE_OP_ERROR(bakpath_p, + "rename"); + } + } + g_free(bakpath_p); + g_free(bakpath_n); + } + } + if (rename_force(path, bakpath) < 0) { + FILE_OP_ERROR(path, "rename"); + ret = -1; + goto finish; + } + } + + if (rename_force(tmppath, path) < 0) { + FILE_OP_ERROR(tmppath, "rename"); + ret = -1; + goto finish; + } + +finish: + if (ret < 0) + g_unlink(tmppath); + g_free(path); + g_free(tmppath); + g_free(bakpath); + return ret; +} + +gint prefs_file_close_revert(PrefFile *pfile) +{ + gchar *tmppath; + + g_return_val_if_fail(pfile != NULL, -1); + + tmppath = g_strconcat(pfile->path, ".tmp", NULL); + fclose(pfile->fp); + if (g_unlink(tmppath) < 0) + FILE_OP_ERROR(tmppath, "unlink"); + g_free(tmppath); + g_free(pfile->path); + g_free(pfile); + + return 0; +} + +void prefs_set_default(PrefParam *param) +{ + gint i; + + g_return_if_fail(param != NULL); + + for (i = 0; param[i].name != NULL; i++) { + if (!param[i].data) continue; + + switch (param[i].type) { + case P_STRING: + if (param[i].defval != NULL) { + if (!g_ascii_strncasecmp(param[i].defval, "ENV_", 4)) { + const gchar *envstr; + gchar *tmp = NULL; + + envstr = g_getenv(param[i].defval + 4); +#ifdef G_OS_WIN32 + tmp = g_strdup(envstr); +#else + if (envstr) { + tmp = conv_codeset_strdup + (envstr, + conv_get_locale_charset_str(), + CS_UTF_8); + if (!tmp) { + g_warning("failed to convert character set."); + tmp = g_strdup(envstr); + } + } +#endif + *((gchar **)param[i].data) = tmp; + } else if (param[i].defval[0] == '~') + *((gchar **)param[i].data) = +#ifdef G_OS_WIN32 + g_strconcat(get_rc_dir(), +#else + g_strconcat(get_home_dir(), +#endif + param[i].defval + 1, + NULL); + else if (param[i].defval[0] != '\0') + *((gchar **)param[i].data) = + g_strdup(param[i].defval); + else + *((gchar **)param[i].data) = NULL; + } else + *((gchar **)param[i].data) = NULL; + break; + case P_INT: + if (param[i].defval != NULL) + *((gint *)param[i].data) = + (gint)atoi(param[i].defval); + else + *((gint *)param[i].data) = 0; + break; + case P_BOOL: + if (param[i].defval != NULL) { + if (!g_ascii_strcasecmp(param[i].defval, "TRUE")) + *((gboolean *)param[i].data) = TRUE; + else + *((gboolean *)param[i].data) = + atoi(param[i].defval) ? TRUE : FALSE; + } else + *((gboolean *)param[i].data) = FALSE; + break; + case P_ENUM: + if (param[i].defval != NULL) + *((DummyEnum*)param[i].data) = + (DummyEnum)atoi(param[i].defval); + else + *((DummyEnum *)param[i].data) = 0; + break; + case P_USHORT: + if (param[i].defval != NULL) + *((gushort *)param[i].data) = + (gushort)atoi(param[i].defval); + else + *((gushort *)param[i].data) = 0; + break; + default: + break; + } + } +} + +void prefs_free(PrefParam *param) +{ + gint i; + + g_return_if_fail(param != NULL); + + for (i = 0; param[i].name != NULL; i++) { + if (!param[i].data) continue; + + switch (param[i].type) { + case P_STRING: + g_free(*((gchar **)param[i].data)); + break; + default: + break; + } + } +} diff --git a/libsylph/prefs.h b/libsylph/prefs.h new file mode 100644 index 0000000..48b1df2 --- /dev/null +++ b/libsylph/prefs.h @@ -0,0 +1,80 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2008 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __PREFS_H__ +#define __PREFS_H__ + +#include +#include + +typedef struct _PrefParam PrefParam; +typedef struct _PrefFile PrefFile; + +#define PREFSBUFSIZE 8192 + +typedef enum +{ + P_STRING, + P_INT, + P_BOOL, + P_ENUM, + P_USHORT, + P_OTHER +} PrefType; + +typedef void (*DataSetFunc) (PrefParam *pparam); +typedef void (*WidgetSetFunc) (PrefParam *pparam); + +struct _PrefParam { + gchar *name; + gchar *defval; + gpointer data; + PrefType type; + gpointer ui_data; +}; + +struct _PrefFile { + FILE *fp; + gchar *path; +}; + +GHashTable *prefs_param_table_get (PrefParam *param); +void prefs_param_table_destroy (GHashTable *param_table); + +void prefs_read_config (PrefParam *param, + const gchar *label, + const gchar *rcfile, + const gchar *encoding); +void prefs_write_config (PrefParam *param, + const gchar *label, + const gchar *rcfile); + +PrefFile *prefs_file_open (const gchar *path); +gint prefs_file_write_param (PrefFile *pfile, + PrefParam *param); +void prefs_file_set_backup_generation (PrefFile *pfile, + gint generation); +gint prefs_file_get_backup_generation (PrefFile *pfile); +gint prefs_file_close (PrefFile *pfile); +gint prefs_file_close_revert (PrefFile *pfile); + +void prefs_set_default (PrefParam *param); +void prefs_free (PrefParam *param); + +#endif /* __PREFS_H__ */ diff --git a/libsylph/prefs_account.c b/libsylph/prefs_account.c new file mode 100644 index 0000000..1aecba9 --- /dev/null +++ b/libsylph/prefs_account.c @@ -0,0 +1,296 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2017 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include + +#include "prefs.h" +#include "prefs_account.h" +#include "customheader.h" +#include "account.h" +#include "utils.h" + +static PrefsAccount tmp_ac_prefs; + +static PrefParam param[] = { + /* Basic */ + {"account_name", NULL, &tmp_ac_prefs.account_name, P_STRING}, + {"is_default", "FALSE", &tmp_ac_prefs.is_default, P_BOOL}, + {"name", NULL, &tmp_ac_prefs.name, P_STRING}, + {"address", NULL, &tmp_ac_prefs.address, P_STRING}, + {"organization", NULL, &tmp_ac_prefs.organization, P_STRING}, + {"protocol", NULL, &tmp_ac_prefs.protocol, P_ENUM}, + {"receive_server", NULL, &tmp_ac_prefs.recv_server, P_STRING}, + {"smtp_server", NULL, &tmp_ac_prefs.smtp_server, P_STRING}, + {"nntp_server", NULL, &tmp_ac_prefs.nntp_server, P_STRING}, + {"use_nntp_auth", "FALSE", &tmp_ac_prefs.use_nntp_auth, P_BOOL}, + {"user_id", "ENV_USER", &tmp_ac_prefs.userid, P_STRING}, + {"password", NULL, &tmp_ac_prefs.passwd, P_STRING}, + {"inbox", "inbox", &tmp_ac_prefs.inbox, P_STRING}, + + /* Receive */ + {"use_apop_auth", "FALSE", &tmp_ac_prefs.use_apop_auth, P_BOOL}, + {"remove_mail", "TRUE", &tmp_ac_prefs.rmmail, P_BOOL}, + {"message_leave_time", "7", &tmp_ac_prefs.msg_leave_time, P_INT}, + {"get_all_mail", "FALSE", &tmp_ac_prefs.getall, P_BOOL}, + {"enable_size_limit", "FALSE", &tmp_ac_prefs.enable_size_limit, P_BOOL}, + {"size_limit", "1024", &tmp_ac_prefs.size_limit, P_INT}, + {"filter_on_receive", "TRUE", &tmp_ac_prefs.filter_on_recv, P_BOOL}, + {"imap_check_inbox_only", "FALSE", &tmp_ac_prefs.imap_check_inbox_only, + P_BOOL}, + {"imap_filter_inbox_on_receive", "FALSE", + &tmp_ac_prefs.imap_filter_inbox_on_recv, P_BOOL}, + {"imap_auth_method", "0", &tmp_ac_prefs.imap_auth_type, P_ENUM}, + {"max_nntp_articles", "300", &tmp_ac_prefs.max_nntp_articles, P_INT}, + {"receive_at_get_all", "TRUE", &tmp_ac_prefs.recv_at_getall, P_BOOL}, + + /* Send */ + {"add_date", "TRUE", &tmp_ac_prefs.add_date, P_BOOL}, + {"generate_msgid", "TRUE", &tmp_ac_prefs.gen_msgid, P_BOOL}, + {"add_custom_header", "FALSE", &tmp_ac_prefs.add_customhdr, P_BOOL}, + {"use_smtp_auth", "FALSE", &tmp_ac_prefs.use_smtp_auth, P_BOOL}, + {"smtp_auth_method", "0", &tmp_ac_prefs.smtp_auth_type, P_ENUM}, + {"smtp_user_id", NULL, &tmp_ac_prefs.smtp_userid, P_STRING}, + {"smtp_password", NULL, &tmp_ac_prefs.smtp_passwd, P_STRING}, + {"pop_before_smtp", "FALSE", &tmp_ac_prefs.pop_before_smtp, P_BOOL}, + + /* Compose */ + {"signature_type", "2", &tmp_ac_prefs.sig_type, P_ENUM}, + {"signature_path", "~" G_DIR_SEPARATOR_S DEFAULT_SIGNATURE, + &tmp_ac_prefs.sig_path, P_STRING}, + {"signature_name", NULL, &tmp_ac_prefs.sig_names[0], P_STRING}, + {"signature_name2", NULL, &tmp_ac_prefs.sig_names[1], P_STRING}, + {"signature_name3", NULL, &tmp_ac_prefs.sig_names[2], P_STRING}, + {"signature_name4", NULL, &tmp_ac_prefs.sig_names[3], P_STRING}, + {"signature_name5", NULL, &tmp_ac_prefs.sig_names[4], P_STRING}, + {"signature_name6", NULL, &tmp_ac_prefs.sig_names[5], P_STRING}, + {"signature_name7", NULL, &tmp_ac_prefs.sig_names[6], P_STRING}, + {"signature_name8", NULL, &tmp_ac_prefs.sig_names[7], P_STRING}, + {"signature_name9", NULL, &tmp_ac_prefs.sig_names[8], P_STRING}, + {"signature_name10", NULL, &tmp_ac_prefs.sig_names[9], P_STRING}, + {"signature_text", NULL, &tmp_ac_prefs.sig_text, P_STRING}, + {"signature_text2", NULL, &tmp_ac_prefs.sig_texts[1], P_STRING}, + {"signature_text3", NULL, &tmp_ac_prefs.sig_texts[2], P_STRING}, + {"signature_text4", NULL, &tmp_ac_prefs.sig_texts[3], P_STRING}, + {"signature_text5", NULL, &tmp_ac_prefs.sig_texts[4], P_STRING}, + {"signature_text6", NULL, &tmp_ac_prefs.sig_texts[5], P_STRING}, + {"signature_text7", NULL, &tmp_ac_prefs.sig_texts[6], P_STRING}, + {"signature_text8", NULL, &tmp_ac_prefs.sig_texts[7], P_STRING}, + {"signature_text9", NULL, &tmp_ac_prefs.sig_texts[8], P_STRING}, + {"signature_text10", NULL, &tmp_ac_prefs.sig_texts[9], P_STRING}, + {"signature_before_quote", "FALSE", &tmp_ac_prefs.sig_before_quote, + P_BOOL}, + {"set_autocc", "FALSE", &tmp_ac_prefs.set_autocc, P_BOOL}, + {"auto_cc", NULL, &tmp_ac_prefs.auto_cc, P_STRING}, + {"set_autobcc", "FALSE", &tmp_ac_prefs.set_autobcc, P_BOOL}, + {"auto_bcc", NULL, &tmp_ac_prefs.auto_bcc, P_STRING}, + {"set_autoreplyto", "FALSE", &tmp_ac_prefs.set_autoreplyto, P_BOOL}, + {"auto_replyto", NULL, &tmp_ac_prefs.auto_replyto, P_STRING}, + + /* Privacy */ + {"default_sign", "FALSE", &tmp_ac_prefs.default_sign, P_BOOL}, + {"default_encrypt", "FALSE", &tmp_ac_prefs.default_encrypt, P_BOOL}, + {"encrypt_reply", "TRUE", &tmp_ac_prefs.encrypt_reply, P_BOOL}, + {"encrypt_to_self", "TRUE", &tmp_ac_prefs.encrypt_to_self, P_BOOL}, + {"ascii_armored", "FALSE", &tmp_ac_prefs.ascii_armored, P_BOOL}, + {"clearsign", "FALSE", &tmp_ac_prefs.clearsign, P_BOOL}, + {"sign_key", NULL, &tmp_ac_prefs.sign_key, P_ENUM}, + {"sign_key_id", NULL, &tmp_ac_prefs.sign_key_id, P_STRING}, + + /* SSL */ + {"ssl_pop", "0", &tmp_ac_prefs.ssl_pop, P_ENUM}, + {"ssl_imap", "0", &tmp_ac_prefs.ssl_imap, P_ENUM}, + {"ssl_nntp", "0", &tmp_ac_prefs.ssl_nntp, P_ENUM}, + {"ssl_smtp", "0", &tmp_ac_prefs.ssl_smtp, P_ENUM}, + {"use_nonblocking_ssl", "1", &tmp_ac_prefs.use_nonblocking_ssl, P_BOOL}, + + /* SOCKS proxy */ + {"use_socks", "FALSE", &tmp_ac_prefs.use_socks, P_BOOL}, + {"use_socks_for_recv", "TRUE", &tmp_ac_prefs.use_socks_for_recv, P_BOOL}, + {"use_socks_for_send", "TRUE", &tmp_ac_prefs.use_socks_for_send, P_BOOL}, + {"socks_type", "1", &tmp_ac_prefs.socks_type, P_ENUM}, + {"proxy_host", NULL, &tmp_ac_prefs.proxy_host, P_STRING}, + {"proxy_port", "1080", &tmp_ac_prefs.proxy_port, P_USHORT}, + {"use_proxy_auth", "FALSE", &tmp_ac_prefs.use_proxy_auth, P_BOOL}, + {"proxy_name", NULL, &tmp_ac_prefs.proxy_name, P_STRING}, + {"proxy_pass", NULL, &tmp_ac_prefs.proxy_pass, P_STRING}, + + /* Advanced */ + {"set_smtpport", "FALSE", &tmp_ac_prefs.set_smtpport, P_BOOL}, + {"smtp_port", "25", &tmp_ac_prefs.smtpport, P_USHORT}, + {"set_popport", "FALSE", &tmp_ac_prefs.set_popport, P_BOOL}, + {"pop_port", "110", &tmp_ac_prefs.popport, P_USHORT}, + {"set_imapport", "FALSE", &tmp_ac_prefs.set_imapport, P_BOOL}, + {"imap_port", "143", &tmp_ac_prefs.imapport, P_USHORT}, + {"set_nntpport", "FALSE", &tmp_ac_prefs.set_nntpport, P_BOOL}, + {"nntp_port", "119", &tmp_ac_prefs.nntpport, P_USHORT}, + {"set_domain", "FALSE", &tmp_ac_prefs.set_domain, P_BOOL}, + {"domain", NULL, &tmp_ac_prefs.domain, P_STRING}, + {"imap_directory", NULL, &tmp_ac_prefs.imap_dir, P_STRING}, + {"imap_clear_cache_on_exit", "FALSE", + &tmp_ac_prefs.imap_clear_cache_on_exit, P_BOOL}, + {"set_sent_folder", "FALSE", &tmp_ac_prefs.set_sent_folder, P_BOOL}, + {"sent_folder", NULL, &tmp_ac_prefs.sent_folder, P_STRING}, + {"set_draft_folder", "FALSE", &tmp_ac_prefs.set_draft_folder, P_BOOL}, + {"draft_folder", NULL, &tmp_ac_prefs.draft_folder, P_STRING}, + {"set_queue_folder", "FALSE", &tmp_ac_prefs.set_queue_folder, P_BOOL}, + {"queue_folder", NULL, &tmp_ac_prefs.queue_folder, P_STRING}, + {"set_trash_folder", "FALSE", &tmp_ac_prefs.set_trash_folder, P_BOOL}, + {"trash_folder", NULL, &tmp_ac_prefs.trash_folder, P_STRING}, + + {NULL, NULL, NULL, P_OTHER} +}; + +static gint prefs_account_get_new_id(void); + + +PrefsAccount *prefs_account_get_tmp_prefs(void) +{ + return &tmp_ac_prefs; +} + +void prefs_account_set_tmp_prefs(PrefsAccount *ac_prefs) +{ + tmp_ac_prefs = *ac_prefs; +} + +void prefs_account_apply_tmp_prefs(PrefsAccount *ac_prefs) +{ + *ac_prefs = tmp_ac_prefs; +} + +PrefParam *prefs_account_get_params(void) +{ + return param; +} + +PrefsAccount *prefs_account_new(void) +{ + PrefsAccount *ac_prefs; + + ac_prefs = g_new0(PrefsAccount, 1); + memset(&tmp_ac_prefs, 0, sizeof(PrefsAccount)); + prefs_set_default(param); + *ac_prefs = tmp_ac_prefs; + ac_prefs->account_id = prefs_account_get_new_id(); + + return ac_prefs; +} + +void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label) +{ + const gchar *p = label; + gchar *rcpath; + gint id; + + g_return_if_fail(ac_prefs != NULL); + g_return_if_fail(label != NULL); + + memset(&tmp_ac_prefs, 0, sizeof(PrefsAccount)); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACCOUNT_RC, NULL); + prefs_read_config(param, label, rcpath, NULL); + g_free(rcpath); + + *ac_prefs = tmp_ac_prefs; + while (*p && !g_ascii_isdigit(*p)) p++; + id = atoi(p); + if (id < 0) g_warning("wrong account id: %d\n", id); + ac_prefs->account_id = id; + + if (ac_prefs->protocol == A_APOP) { + debug_print("converting protocol A_APOP to new prefs.\n"); + ac_prefs->protocol = A_POP3; + ac_prefs->use_apop_auth = TRUE; + } + + custom_header_read_config(ac_prefs); +} + +void prefs_account_write_config_all(GList *account_list) +{ + GList *cur; + gchar *rcpath; + PrefFile *pfile; + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACCOUNT_RC, NULL); + if ((pfile = prefs_file_open(rcpath)) == NULL) { + g_free(rcpath); + return; + } + g_free(rcpath); + + for (cur = account_list; cur != NULL; cur = cur->next) { + tmp_ac_prefs = *(PrefsAccount *)cur->data; + if (fprintf(pfile->fp, "[Account: %d]\n", + tmp_ac_prefs.account_id) <= 0 || + prefs_file_write_param(pfile, param) < 0) { + g_warning(_("failed to write configuration to file\n")); + prefs_file_close_revert(pfile); + return; + } + if (cur->next) { + if (fputc('\n', pfile->fp) == EOF) { + FILE_OP_ERROR(rcpath, "fputc"); + prefs_file_close_revert(pfile); + return; + } + } + } + + if (prefs_file_close(pfile) < 0) + g_warning(_("failed to write configuration to file\n")); +} + +void prefs_account_free(PrefsAccount *ac_prefs) +{ + if (!ac_prefs) return; + + tmp_ac_prefs = *ac_prefs; + prefs_free(param); + + if (ac_prefs->tmp_pass) + g_free(ac_prefs->tmp_pass); + g_free(ac_prefs); +} + +static gint prefs_account_get_new_id(void) +{ + GList *ac_list; + PrefsAccount *ac; + static gint last_id = 0; + + for (ac_list = account_get_list(); ac_list != NULL; + ac_list = ac_list->next) { + ac = (PrefsAccount *)ac_list->data; + if (last_id < ac->account_id) + last_id = ac->account_id; + } + + return last_id + 1; +} diff --git a/libsylph/prefs_account.h b/libsylph/prefs_account.h new file mode 100644 index 0000000..ad899f8 --- /dev/null +++ b/libsylph/prefs_account.h @@ -0,0 +1,205 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2017 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __PREFS_ACCOUNT_H__ +#define __PREFS_ACCOUNT_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +typedef struct _PrefsAccount PrefsAccount; + +#include "folder.h" +#include "smtp.h" +#include "prefs.h" + +typedef enum { + A_POP3, + A_APOP, /* deprecated */ + A_RPOP, /* deprecated */ + A_IMAP4, + A_NNTP, + A_LOCAL +} RecvProtocol; + +typedef enum { + SIG_FILE, + SIG_COMMAND, + SIG_DIRECT +} SigType; + +typedef enum { + SIGN_KEY_DEFAULT, + SIGN_KEY_BY_FROM, + SIGN_KEY_CUSTOM +} SignKeyType; + +struct _PrefsAccount +{ + gchar *account_name; + + /* Personal info */ + gchar *name; + gchar *address; + gchar *organization; + + /* Server info */ + RecvProtocol protocol; + gchar *recv_server; + gchar *smtp_server; + gchar *nntp_server; + gboolean use_nntp_auth; + gchar *userid; + gchar *passwd; + + /* SSL */ + gint ssl_pop; + gint ssl_imap; + gint ssl_nntp; + gint ssl_smtp; + gboolean use_nonblocking_ssl; + + /* Temporarily preserved password */ + gchar *tmp_pass; + + /* Receive */ + gboolean use_apop_auth; + gboolean rmmail; + gint msg_leave_time; + gboolean getall; + gboolean enable_size_limit; + gint size_limit; + gboolean filter_on_recv; + gchar *inbox; + + gboolean imap_check_inbox_only; + gboolean imap_filter_inbox_on_recv; + gint imap_auth_type; + + gint max_nntp_articles; + + gboolean recv_at_getall; + + /* Send */ + gboolean add_date; + gboolean gen_msgid; + gboolean add_customhdr; + gboolean use_smtp_auth; + SMTPAuthType smtp_auth_type; + gchar *smtp_userid; + gchar *smtp_passwd; + + /* Temporarily preserved password */ + gchar *tmp_smtp_pass; + + gboolean pop_before_smtp; + + GSList *customhdr_list; + + /* Compose */ + SigType sig_type; + gchar *sig_path; + gchar *sig_text; + gboolean set_autocc; + gchar *auto_cc; + gboolean set_autobcc; + gchar *auto_bcc; + gboolean set_autoreplyto; + gchar *auto_replyto; + + /* Privacy */ + gboolean default_sign; + gboolean default_encrypt; + gboolean ascii_armored; + gboolean clearsign; + gboolean encrypt_reply; + + SignKeyType sign_key; + gchar *sign_key_id; + + /* Advanced */ + gboolean set_smtpport; + gushort smtpport; + gboolean set_popport; + gushort popport; + gboolean set_imapport; + gushort imapport; + gboolean set_nntpport; + gushort nntpport; + gboolean set_domain; + gchar *domain; + + gchar *imap_dir; + gboolean imap_clear_cache_on_exit; + + gboolean set_sent_folder; + gchar *sent_folder; + gboolean set_draft_folder; + gchar *draft_folder; + gboolean set_queue_folder; + gchar *queue_folder; + gboolean set_trash_folder; + gchar *trash_folder; + + /* Default or not */ + gboolean is_default; + /* Unique account ID */ + gint account_id; + + RemoteFolder *folder; + + /* Compose */ + gboolean sig_before_quote; + + /* SOCKS proxy */ + gboolean use_socks; + gboolean use_socks_for_recv; + gboolean use_socks_for_send; + gint socks_type; + gchar *proxy_host; + gushort proxy_port; + gboolean use_proxy_auth; + gchar *proxy_name; + gchar *proxy_pass; + + /* Privacy */ + gboolean encrypt_to_self; + + /* Compose */ + gchar *sig_names[10]; + gchar *sig_texts[10]; +}; + +PrefsAccount *prefs_account_new (void); + +PrefsAccount *prefs_account_get_tmp_prefs (void); +void prefs_account_set_tmp_prefs (PrefsAccount *ac_prefs); +void prefs_account_apply_tmp_prefs (PrefsAccount *ac_prefs); +PrefParam *prefs_account_get_params (void); + +void prefs_account_read_config (PrefsAccount *ac_prefs, + const gchar *label); +void prefs_account_write_config_all (GList *account_list); + +void prefs_account_free (PrefsAccount *ac_prefs); + +#endif /* __PREFS_ACCOUNT_H__ */ diff --git a/libsylph/prefs_common.c b/libsylph/prefs_common.c new file mode 100644 index 0000000..8ed74a3 --- /dev/null +++ b/libsylph/prefs_common.c @@ -0,0 +1,634 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2017 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include + +#include "prefs.h" +#include "prefs_common.h" +#include "filter.h" +#include "codeconv.h" +#include "utils.h" + +PrefsCommon prefs_common; + +static PrefParam param[] = { + /* Receive */ + {"autochk_newmail", "FALSE", &prefs_common.autochk_newmail, P_BOOL}, + {"autochk_interval", "10", &prefs_common.autochk_itv, P_INT}, + {"check_on_startup", "FALSE", &prefs_common.chk_on_startup, P_BOOL}, + {"scan_all_after_inc", "FALSE", &prefs_common.scan_all_after_inc, + P_BOOL}, + {"enable_newmsg_notify", "FALSE", &prefs_common.enable_newmsg_notify, + P_BOOL}, + {"newmsg_notify_command", NULL, &prefs_common.newmsg_notify_cmd, + P_STRING}, + {"enable_newmsg_notify_sound", "FALSE", + &prefs_common.enable_newmsg_notify_sound, P_BOOL}, + {"newmsg_notify_sound", NULL, &prefs_common.newmsg_notify_sound, + P_STRING}, + {"enable_newmsg_notify_window", "TRUE", + &prefs_common.enable_newmsg_notify_window, P_BOOL}, + {"notify_window_period", "10", + &prefs_common.notify_window_period, P_INT}, + + {"inc_local", "FALSE", &prefs_common.inc_local, P_BOOL}, + {"filter_on_inc_local", "TRUE", &prefs_common.filter_on_inc, P_BOOL}, + {"spool_path", DEFAULT_SPOOL_PATH, &prefs_common.spool_path, P_STRING}, + + /* Send */ + {"save_message", "TRUE", &prefs_common.savemsg, P_BOOL}, + {"filter_sent_message", "FALSE", &prefs_common.filter_sent, P_BOOL}, + {"recipients_autoreg", "TRUE", &prefs_common.recipients_autoreg, + P_BOOL}, + {"show_send_dialog", "TRUE", &prefs_common.show_send_dialog, P_BOOL}, + + {"encoding_method", "0", &prefs_common.encoding_method, P_ENUM}, + {"mime_filename_encoding_method", "0", + &prefs_common.mime_fencoding_method, P_ENUM}, + {"check_attach", "FALSE", &prefs_common.check_attach, P_BOOL}, + {"check_attach_str", NULL, &prefs_common.check_attach_str, P_STRING}, + {"check_recipients", "FALSE", &prefs_common.check_recipients, P_BOOL}, + {"check_recp_exclude", NULL, &prefs_common.check_recp_exclude, + P_STRING}, + + {"allow_jisx0201_kana", "FALSE", &prefs_common.allow_jisx0201_kana, + P_BOOL}, + + /* Compose */ + {"auto_signature", "TRUE", &prefs_common.auto_sig, P_BOOL}, + {"signature_separator", "-- ", &prefs_common.sig_sep, P_STRING}, + + {"auto_ext_editor", "FALSE", &prefs_common.auto_exteditor, P_BOOL}, + + {"undo_level", "50", &prefs_common.undolevels, P_INT}, + + {"linewrap_length", "72", &prefs_common.linewrap_len, P_INT}, + {"linewrap_quotation", "FALSE", &prefs_common.linewrap_quote, P_BOOL}, + {"linewrap_auto", "FALSE", &prefs_common.autowrap, P_BOOL}, + + {"enable_autosave", "FALSE", &prefs_common.enable_autosave, P_BOOL}, + {"autosave_interval", "5", &prefs_common.autosave_itv, P_INT}, + + {"reply_with_quote", "TRUE", &prefs_common.reply_with_quote, P_BOOL}, + {"reply_account_autoselect", "TRUE", + &prefs_common.reply_account_autosel, P_BOOL}, + {"default_reply_list", "TRUE", &prefs_common.default_reply_list, + P_BOOL}, + {"inherit_recipient_on_self_reply", "FALSE", + &prefs_common.inherit_recipient_on_self_reply, P_BOOL}, + {"reply_address_only", "FALSE", + &prefs_common.reply_address_only, P_BOOL}, + + {"show_ruler", "TRUE", &prefs_common.show_ruler, P_BOOL}, + + /* Quote */ + {"reply_quote_mark", "> ", &prefs_common.quotemark, P_STRING}, + {"reply_quote_format", "On %d\\n%f wrote:\\n\\n%Q", + &prefs_common.quotefmt, P_STRING}, + + {"forward_quote_mark", "> ", &prefs_common.fw_quotemark, P_STRING}, + {"forward_quote_format", + "\\n\\nBegin forwarded message:\\n\\n" + "?d{Date: %d\\n}?f{From: %f\\n}?t{To: %t\\n}?c{Cc: %c\\n}" + "?n{Newsgroups: %n\\n}?s{Subject: %s\\n}\\n\\n%M", + &prefs_common.fw_quotefmt, P_STRING}, + + /* Spelling */ + {"check_spell", "FALSE", &prefs_common.check_spell, P_BOOL}, + {"spell_lang", "en", &prefs_common.spell_lang, P_STRING}, + + /* Display */ + {"message_font_name", DEFAULT_MESSAGE_FONT, &prefs_common.textfont, + P_STRING}, + + {"display_folder_unread_num", "TRUE", + &prefs_common.display_folder_unread, P_BOOL}, + {"display_folder_num_columns", "FALSE", + &prefs_common.display_folder_num_columns, P_BOOL}, + {"folder_col_show_new", "FALSE", + &prefs_common.folder_col_visible[1], P_BOOL}, + {"folder_col_show_unread", "FALSE", + &prefs_common.folder_col_visible[2], P_BOOL}, + {"folder_col_show_total", "FALSE", + &prefs_common.folder_col_visible[3], P_BOOL}, + + {"newsgroup_abbrev_len", "16", &prefs_common.ng_abbrev_len, P_INT}, + + {"translate_header", "TRUE", &prefs_common.trans_hdr, P_BOOL}, + + /* Display: Summary View */ + {"enable_swap_from", "FALSE", &prefs_common.swap_from, P_BOOL}, + {"date_format", "%y/%m/%d(%a) %H:%M", &prefs_common.date_format, + P_STRING}, + {"expand_thread", "TRUE", &prefs_common.expand_thread, P_BOOL}, + + {"enable_rules_hint", "TRUE", &prefs_common.enable_rules_hint, P_BOOL}, + {"bold_unread", "TRUE", &prefs_common.bold_unread, P_BOOL}, + + {"persist_qsearch_filter", "TRUE", &prefs_common.persist_qsearch_filter, + P_BOOL}, + + {"toolbar_style", "4", &prefs_common.toolbar_style, P_ENUM}, + {"show_searchbar", "TRUE", &prefs_common.show_searchbar, P_BOOL}, + {"show_statusbar", "TRUE", &prefs_common.show_statusbar, P_BOOL}, + + {"main_toolbar_setting", NULL, &prefs_common.main_toolbar_setting, + P_STRING}, + {"compose_toolbar_setting", NULL, + &prefs_common.compose_toolbar_setting, P_STRING}, + + {"layout_type", "0", &prefs_common.layout_type, P_ENUM}, + + {"summary_col_show_mark", "TRUE", + &prefs_common.summary_col_visible[S_COL_MARK], P_BOOL}, + {"summary_col_show_unread", "TRUE", + &prefs_common.summary_col_visible[S_COL_UNREAD], P_BOOL}, + {"summary_col_show_mime", "TRUE", + &prefs_common.summary_col_visible[S_COL_MIME], P_BOOL}, + {"summary_col_show_subject", "TRUE", + &prefs_common.summary_col_visible[S_COL_SUBJECT], P_BOOL}, + {"summary_col_show_from", "TRUE", + &prefs_common.summary_col_visible[S_COL_FROM], P_BOOL}, + {"summary_col_show_date", "TRUE", + &prefs_common.summary_col_visible[S_COL_DATE], P_BOOL}, + {"summary_col_show_size", "TRUE", + &prefs_common.summary_col_visible[S_COL_SIZE], P_BOOL}, + {"summary_col_show_number", "FALSE", + &prefs_common.summary_col_visible[S_COL_NUMBER], P_BOOL}, + {"summary_col_show_to", "FALSE", + &prefs_common.summary_col_visible[S_COL_TO], P_BOOL}, + + {"summary_col_pos_mark", "0", + &prefs_common.summary_col_pos[S_COL_MARK], P_INT}, + {"summary_col_pos_unread", "1", + &prefs_common.summary_col_pos[S_COL_UNREAD], P_INT}, + {"summary_col_pos_mime", "2", + &prefs_common.summary_col_pos[S_COL_MIME], P_INT}, + {"summary_col_pos_subject", "3", + &prefs_common.summary_col_pos[S_COL_SUBJECT], P_INT}, + {"summary_col_pos_from", "4", + &prefs_common.summary_col_pos[S_COL_FROM], P_INT}, + {"summary_col_pos_date", "5", + &prefs_common.summary_col_pos[S_COL_DATE], P_INT}, + {"summary_col_pos_size", "6", + &prefs_common.summary_col_pos[S_COL_SIZE], P_INT}, + {"summary_col_pos_number", "7", + &prefs_common.summary_col_pos[S_COL_NUMBER], P_INT}, + {"summary_col_pos_to", "8", + &prefs_common.summary_col_pos[S_COL_TO], P_INT}, + + {"summary_sent_col_show_mark", "TRUE", + &prefs_common.summary_sent_col_visible[S_COL_MARK], P_BOOL}, + {"summary_sent_col_show_unread", "TRUE", + &prefs_common.summary_sent_col_visible[S_COL_UNREAD], P_BOOL}, + {"summary_sent_col_show_mime", "TRUE", + &prefs_common.summary_sent_col_visible[S_COL_MIME], P_BOOL}, + {"summary_sent_col_show_subject", "TRUE", + &prefs_common.summary_sent_col_visible[S_COL_SUBJECT], P_BOOL}, + {"summary_sent_col_show_from", "FALSE", + &prefs_common.summary_sent_col_visible[S_COL_FROM], P_BOOL}, + {"summary_sent_col_show_date", "TRUE", + &prefs_common.summary_sent_col_visible[S_COL_DATE], P_BOOL}, + {"summary_sent_col_show_size", "TRUE", + &prefs_common.summary_sent_col_visible[S_COL_SIZE], P_BOOL}, + {"summary_sent_col_show_number", "FALSE", + &prefs_common.summary_sent_col_visible[S_COL_NUMBER], P_BOOL}, + {"summary_sent_col_show_to", "TRUE", + &prefs_common.summary_sent_col_visible[S_COL_TO], P_BOOL}, + + {"summary_sent_col_pos_mark", "0", + &prefs_common.summary_sent_col_pos[S_COL_MARK], P_INT}, + {"summary_sent_col_pos_unread", "1", + &prefs_common.summary_sent_col_pos[S_COL_UNREAD], P_INT}, + {"summary_sent_col_pos_mime", "2", + &prefs_common.summary_sent_col_pos[S_COL_MIME], P_INT}, + {"summary_sent_col_pos_subject", "3", + &prefs_common.summary_sent_col_pos[S_COL_SUBJECT], P_INT}, + {"summary_sent_col_pos_from", "8", + &prefs_common.summary_sent_col_pos[S_COL_FROM], P_INT}, + {"summary_sent_col_pos_date", "5", + &prefs_common.summary_sent_col_pos[S_COL_DATE], P_INT}, + {"summary_sent_col_pos_size", "6", + &prefs_common.summary_sent_col_pos[S_COL_SIZE], P_INT}, + {"summary_sent_col_pos_number", "7", + &prefs_common.summary_sent_col_pos[S_COL_NUMBER], P_INT}, + {"summary_sent_col_pos_to", "4", + &prefs_common.summary_sent_col_pos[S_COL_TO], P_INT}, + + {"summary_col_size_mark", "10", + &prefs_common.summary_col_size[S_COL_MARK], P_INT}, + {"summary_col_size_unread", "13", + &prefs_common.summary_col_size[S_COL_UNREAD], P_INT}, + {"summary_col_size_mime", "10", + &prefs_common.summary_col_size[S_COL_MIME], P_INT}, + {"summary_col_size_subject", "200", + &prefs_common.summary_col_size[S_COL_SUBJECT], P_INT}, + {"summary_col_size_from", "120", + &prefs_common.summary_col_size[S_COL_FROM], P_INT}, + {"summary_col_size_date", "118", + &prefs_common.summary_col_size[S_COL_DATE], P_INT}, + {"summary_col_size_size", "45", + &prefs_common.summary_col_size[S_COL_SIZE], P_INT}, + {"summary_col_size_number", "40", + &prefs_common.summary_col_size[S_COL_NUMBER], P_INT}, + {"summary_col_size_to", "120", + &prefs_common.summary_col_size[S_COL_TO], P_INT}, + + /* Widget size */ + {"folderwin_x", "16", &prefs_common.folderwin_x, P_INT}, + {"folderwin_y", "16", &prefs_common.folderwin_y, P_INT}, + {"folderview_width", "179", &prefs_common.folderview_width, P_INT}, + {"folderview_height", "450", &prefs_common.folderview_height, P_INT}, + {"folderview_visible", "TRUE", &prefs_common.folderview_visible, + P_BOOL}, + + {"folder_col_folder", "150", &prefs_common.folder_col_folder, P_INT}, + {"folder_col_new", "32", &prefs_common.folder_col_new, P_INT}, + {"folder_col_unread", "32", &prefs_common.folder_col_unread, P_INT}, + {"folder_col_total", "32", &prefs_common.folder_col_total, P_INT}, + + {"summaryview_width", "600", &prefs_common.summaryview_width, P_INT}, + {"summaryview_height", "180", &prefs_common.summaryview_height, P_INT}, + {"summaryview_vwidth", "300", &prefs_common.summaryview_vwidth, P_INT}, + {"summaryview_vheight", "600", &prefs_common.summaryview_vheight, + P_INT}, + + {"main_messagewin_x", "256", &prefs_common.main_msgwin_x, P_INT}, + {"main_messagewin_y", "210", &prefs_common.main_msgwin_y, P_INT}, + {"messageview_width", "600", &prefs_common.msgview_width, P_INT}, + {"messageview_height", "300", &prefs_common.msgview_height, P_INT}, + {"messageview_vwidth", "300", &prefs_common.msgview_vwidth, P_INT}, + {"messageview_vheight", "600", &prefs_common.msgview_vheight, P_INT}, + {"messageview_visible", "TRUE", &prefs_common.msgview_visible, P_BOOL}, + + {"mainview_x", "64", &prefs_common.mainview_x, P_INT}, + {"mainview_y", "64", &prefs_common.mainview_y, P_INT}, + {"mainview_width", "600", &prefs_common.mainview_width, P_INT}, + {"mainview_height", "600", &prefs_common.mainview_height, P_INT}, + {"mainwin_x", "64", &prefs_common.mainwin_x, P_INT}, + {"mainwin_y", "64", &prefs_common.mainwin_y, P_INT}, + {"mainwin_width", "800", &prefs_common.mainwin_width, P_INT}, + {"mainwin_height", "600", &prefs_common.mainwin_height, P_INT}, + {"messagewin_width", "600", &prefs_common.msgwin_width, P_INT}, + {"messagewin_height", "540", &prefs_common.msgwin_height, P_INT}, + + {"mainwin_maximized", "FALSE", &prefs_common.mainwin_maximized, P_BOOL}, + + {"sourcewin_width", "600", &prefs_common.sourcewin_width, P_INT}, + {"sourcewin_height", "500", &prefs_common.sourcewin_height, P_INT}, + + {"compose_x", "32", &prefs_common.compose_x, P_INT}, + {"compose_y", "32", &prefs_common.compose_y, P_INT}, + {"compose_width", "600", &prefs_common.compose_width, P_INT}, + {"compose_height", "560", &prefs_common.compose_height, P_INT}, + + {"compose_maximized", "FALSE", &prefs_common.compose_maximized, P_BOOL}, + + {"addressbook_x", "32", &prefs_common.addressbook_x, P_INT}, + {"addressbook_y", "32", &prefs_common.addressbook_y, P_INT}, + {"addressbook_width", "620", &prefs_common.addressbook_width, P_INT}, + {"addressbook_height", "360", &prefs_common.addressbook_height, P_INT}, + {"addressbook_folder_width", "190", &prefs_common.addressbook_folder_width, P_INT}, + {"addressbook_col_name", "164", &prefs_common.addressbook_col_name, P_INT}, + {"addressbook_col_addr", "156", &prefs_common.addressbook_col_addr, P_INT}, + {"addressbook_col_nickname", "120", &prefs_common.addressbook_col_nickname, P_INT}, + {"addressbook_col_rem", "100", &prefs_common.addressbook_col_rem, P_INT}, + + /* Message */ + {"enable_color", "TRUE", &prefs_common.enable_color, P_BOOL}, + + {"quote_level1_color", "179", &prefs_common.quote_level1_col, P_INT}, + {"quote_level2_color", "179", &prefs_common.quote_level2_col, P_INT}, + {"quote_level3_color", "179", &prefs_common.quote_level3_col, P_INT}, + {"uri_color", "32512", &prefs_common.uri_col, P_INT}, + {"signature_color", "0", &prefs_common.sig_col, P_USHORT}, + {"recycle_quote_colors", "FALSE", &prefs_common.recycle_quote_colors, + P_BOOL}, + + {"convert_mb_alnum", "FALSE", &prefs_common.conv_mb_alnum, P_BOOL}, + {"display_header_pane", "TRUE", &prefs_common.display_header_pane, + P_BOOL}, + {"show_attach_tab", "FALSE", &prefs_common.show_attach_tab, P_BOOL}, + {"show_attached_files_first", "TRUE", + &prefs_common.show_attached_files_first, P_BOOL}, + {"attach_toolbutton_pos", "0", &prefs_common.attach_toolbutton_pos, + P_INT}, + {"display_header", "TRUE", &prefs_common.display_header, P_BOOL}, + {"render_html", "TRUE", &prefs_common.render_html, P_BOOL}, + {"alt_prefer_html", "FALSE", &prefs_common.alt_prefer_html, P_BOOL}, + {"html_only_as_attach", "FALSE", &prefs_common.html_only_as_attach, + P_BOOL}, + {"line_space", "2", &prefs_common.line_space, P_INT}, + + {"textview_cursor_visible", "FALSE", + &prefs_common.textview_cursor_visible, P_BOOL}, + + {"enable_smooth_scroll", "FALSE", &prefs_common.enable_smooth_scroll, + P_BOOL}, + {"scroll_step", "1", &prefs_common.scroll_step, P_INT}, + {"scroll_half_page", "FALSE", &prefs_common.scroll_halfpage, P_BOOL}, + + {"resize_image", "TRUE", &prefs_common.resize_image, P_BOOL}, + {"inline_image", "TRUE", &prefs_common.inline_image, P_BOOL}, + + /* Encoding */ + {"default_encoding", NULL, &prefs_common.default_encoding, P_STRING}, + {"outgoing_charset", NULL, &prefs_common.outgoing_charset, P_STRING}, + + {"show_other_header", "FALSE", &prefs_common.show_other_header, P_BOOL}, + + /* MIME viewer */ + {"mime_image_viewer", NULL, &prefs_common.mime_image_viewer, P_STRING}, + {"mime_audio_player", NULL, &prefs_common.mime_audio_player, P_STRING}, +#ifdef G_OS_WIN32 + {"mime_open_command", "notepad '%s'", &prefs_common.mime_open_cmd, +#else + {"mime_open_command", "gedit '%s'", &prefs_common.mime_open_cmd, +#endif + P_STRING}, + {"mime_command", NULL, &prefs_common.mime_cmd, P_STRING}, + + /* Junk mail */ + {"enable_junk", "FALSE", &prefs_common.enable_junk, P_BOOL}, +#ifdef G_OS_WIN32 + {"junk_learn_command", "sylfilter -j", + &prefs_common.junk_learncmd, P_STRING}, + {"nojunk_learn_command", "sylfilter -c", + &prefs_common.nojunk_learncmd, P_STRING}, + {"junk_classify_command", "sylfilter", + &prefs_common.junk_classify_cmd, P_STRING}, +#else + {"junk_learn_command", "bogofilter -N -s -I", + &prefs_common.junk_learncmd, P_STRING}, + {"nojunk_learn_command", "bogofilter -n -S -I", + &prefs_common.nojunk_learncmd, P_STRING}, + {"junk_classify_command", "bogofilter -I", + &prefs_common.junk_classify_cmd, P_STRING}, +#endif + {"junk_folder", NULL, &prefs_common.junk_folder, P_STRING}, + {"filter_junk_on_receive", "FALSE", &prefs_common.filter_junk_on_recv, + P_BOOL}, + {"filter_junk_before", "FALSE", &prefs_common.filter_junk_before, + P_BOOL}, + {"delete_junk_on_receive", "TRUE", &prefs_common.delete_junk_on_recv, + P_BOOL}, + {"nofilter_junk_sender_in_book", "TRUE", + &prefs_common.nofilter_junk_sender_in_book, P_BOOL}, + {"mark_junk_as_read", "FALSE", &prefs_common.mark_junk_as_read, P_BOOL}, + + /* Privacy */ + {"auto_check_signatures", "TRUE", &prefs_common.auto_check_signatures, + P_BOOL}, + {"gpg_signature_popup", "FALSE", &prefs_common.gpg_signature_popup, + P_BOOL}, + {"store_passphrase", "FALSE", &prefs_common.store_passphrase, P_BOOL}, + {"store_passphrase_timeout", "0", + &prefs_common.store_passphrase_timeout, P_INT}, + {"passphrase_grab", "FALSE", &prefs_common.passphrase_grab, P_BOOL}, +#ifdef G_OS_WIN32 + {"show_gpg_warning", "FALSE", &prefs_common.gpg_warning, P_BOOL}, +#else + {"show_gpg_warning", "TRUE", &prefs_common.gpg_warning, P_BOOL}, +#endif + + /* Interface */ + {"separate_folder", "FALSE", &prefs_common.sep_folder, P_BOOL}, + {"separate_message", "FALSE", &prefs_common.sep_msg, P_BOOL}, + + {"always_show_message_when_selected", "TRUE", + &prefs_common.always_show_msg, P_BOOL}, + {"open_unread_on_enter", "FALSE", &prefs_common.open_unread_on_enter, + P_BOOL}, + {"always_mark_read_on_show_msg", "FALSE", + &prefs_common.always_mark_read_on_show_msg, P_BOOL}, + {"remember_last_selected", "FALSE", + &prefs_common.remember_last_selected, P_BOOL}, + {"mark_as_read_on_new_window", "FALSE", + &prefs_common.mark_as_read_on_new_window, P_BOOL}, + {"open_inbox_on_inc", "FALSE", &prefs_common.open_inbox_on_inc, P_BOOL}, + {"open_inbox_on_startup", "FALSE", &prefs_common.open_inbox_on_startup, + P_BOOL}, + {"change_account_on_folder_selection", "TRUE", + &prefs_common.change_account_on_folder_sel, P_BOOL}, + {"immediate_execution", "TRUE", &prefs_common.immediate_exec, P_BOOL}, + +#ifdef G_OS_WIN32 + {"comply_gnome_hig", "FALSE", &prefs_common.comply_gnome_hig, P_BOOL}, +#else + {"comply_gnome_hig", "TRUE", &prefs_common.comply_gnome_hig, P_BOOL}, +#endif + + {"show_trayicon", "TRUE", &prefs_common.show_trayicon, P_BOOL}, + {"minimize_to_tray", "FALSE", &prefs_common.minimize_to_tray, P_BOOL}, +#ifdef G_OS_WIN32 + {"toggle_window_on_trayicon_click", "FALSE", +#else + {"toggle_window_on_trayicon_click", "TRUE", +#endif + &prefs_common.toggle_window_on_trayicon_click, P_BOOL}, + + /* Other */ + {"receive_dialog_mode", "1", &prefs_common.recv_dialog_mode, P_ENUM}, + {"no_receive_error_panel", "FALSE", &prefs_common.no_recv_err_panel, + P_BOOL}, + {"close_receive_dialog", "TRUE", &prefs_common.close_recv_dialog, + P_BOOL}, + + {"add_address_by_click", "FALSE", &prefs_common.add_address_by_click, + P_BOOL}, + {"enable_address_completion", "TRUE", + &prefs_common.enable_address_completion, P_BOOL}, + {"fullauto_completion_mode", "TRUE", + &prefs_common.fullauto_completion_mode, P_BOOL}, + {"always_add_address_only", "FALSE", + &prefs_common.always_add_address_only, P_BOOL}, + + {"confirm_on_exit", "FALSE", &prefs_common.confirm_on_exit, P_BOOL}, + {"clean_trash_on_exit", "FALSE", &prefs_common.clean_on_exit, P_BOOL}, + {"ask_on_cleaning", "TRUE", &prefs_common.ask_on_clean, P_BOOL}, + {"warn_queued_on_exit", "TRUE", &prefs_common.warn_queued_on_exit, + P_BOOL}, + + {"logwindow_line_limit", "1000", &prefs_common.logwin_line_limit, + P_INT}, + + {"online_mode", "TRUE", &prefs_common.online_mode, P_BOOL}, + {"startup_online_mode", "1", &prefs_common.startup_online_mode, P_INT}, + + /* External commands */ +#ifdef G_OS_WIN32 + {"uri_open_command", NULL, &prefs_common.uri_cmd, +#else + {"uri_open_command", DEFAULT_BROWSER_CMD, &prefs_common.uri_cmd, +#endif + P_STRING}, +#ifdef G_OS_WIN32 + {"ext_editor_command", "notepad '%s'", &prefs_common.ext_editor_cmd, +#else + {"ext_editor_command", "gedit %s", &prefs_common.ext_editor_cmd, +#endif + P_STRING}, + + {"use_print_command", "FALSE", &prefs_common.use_print_cmd, P_BOOL}, +#ifdef G_OS_WIN32 + {"print_command", NULL, &prefs_common.print_cmd, P_STRING}, +#else + {"print_command", "lpr %s", &prefs_common.print_cmd, P_STRING}, +#endif + + {"use_ext_inc", "FALSE", &prefs_common.use_extinc, P_BOOL}, + {"ext_inc_path", DEFAULT_INC_PATH, &prefs_common.extinc_cmd, P_STRING}, + {"use_ext_sendmail", "FALSE", &prefs_common.use_extsend, P_BOOL}, + {"ext_sendmail_cmd", DEFAULT_SENDMAIL_CMD, &prefs_common.extsend_cmd, + P_STRING}, + + /* Update check */ + {"auto_update_check", "TRUE", &prefs_common.auto_update_check, P_BOOL}, + {"use_http_proxy", "FALSE", &prefs_common.use_http_proxy, P_BOOL}, + {"http_proxy_host", NULL, &prefs_common.http_proxy_host, P_STRING}, + + /* Advanced */ + {"strict_cache_check", "FALSE", &prefs_common.strict_cache_check, + P_BOOL}, + {"io_timeout_secs", "60", &prefs_common.io_timeout_secs, P_INT}, + + /* File selector */ + {"filesel_prev_open_dir", NULL, &prefs_common.prev_open_dir, P_STRING}, + {"filesel_prev_save_dir", NULL, &prefs_common.prev_save_dir, P_STRING}, + {"filesel_prev_folder_dir", NULL, &prefs_common.prev_folder_dir, P_STRING}, + {"filesel_save_file_type", "0", &prefs_common.save_file_type, P_INT}, + + {NULL, NULL, NULL, P_OTHER} +}; + + +PrefsCommon *prefs_common_get(void) +{ + return &prefs_common; +} + +PrefParam *prefs_common_get_params(void) +{ + return param; +} + +void prefs_common_read_config(void) +{ + FILE *fp; + gchar *path; + gchar buf[PREFSBUFSIZE]; + + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL); + + prefs_read_config(param, "Common", path, NULL); + + g_free(path); + + //prefs_common.online_mode = TRUE; + + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMAND_HISTORY, + NULL); + if ((fp = g_fopen(path, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(path, "fopen"); + g_free(path); + return; + } + g_free(path); + while (fgets(buf, sizeof(buf), fp) != NULL) { + g_strstrip(buf); + if (buf[0] == '\0') continue; + prefs_common.mime_open_cmd_history = + add_history(prefs_common.mime_open_cmd_history, buf); + } + fclose(fp); + + prefs_common.mime_open_cmd_history = + g_list_reverse(prefs_common.mime_open_cmd_history); +} + +void prefs_common_write_config(void) +{ + GList *cur; + FILE *fp; + gchar *path; + + prefs_write_config(param, "Common", COMMON_RC); + + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMAND_HISTORY, + NULL); + if ((fp = g_fopen(path, "wb")) == NULL) { + FILE_OP_ERROR(path, "fopen"); + g_free(path); + return; + } + + for (cur = prefs_common.mime_open_cmd_history; + cur != NULL; cur = cur->next) { + fputs((gchar *)cur->data, fp); + fputc('\n', fp); + } + + fclose(fp); + g_free(path); +} + +void prefs_common_junk_filter_list_set(void) +{ +} + +void prefs_common_junk_folder_rename_path(const gchar *old_path, + const gchar *new_path) +{ + gint len; + gchar *base; + gchar *dest_path; + + g_return_if_fail(old_path != NULL); + g_return_if_fail(new_path != NULL); + + if (!prefs_common.junk_folder) + return; + + len = strlen(old_path); + + if (!strncmp(old_path, prefs_common.junk_folder, len)) { + base = prefs_common.junk_folder + len; + if (*base != '/' && *base != '\0') + return; + while (*base == '/') base++; + if (*base == '\0') + dest_path = g_strdup(new_path); + else + dest_path = g_strconcat(new_path, "/", base, NULL); + debug_print("prefs_common_junk_folder_rename_path(): " + "renaming %s -> %s\n", prefs_common.junk_folder, + dest_path); + g_free(prefs_common.junk_folder); + prefs_common.junk_folder = dest_path; + } +} diff --git a/libsylph/prefs_common.h b/libsylph/prefs_common.h new file mode 100644 index 0000000..ba9ddb5 --- /dev/null +++ b/libsylph/prefs_common.h @@ -0,0 +1,375 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2017 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __PREFS_COMMON_H__ +#define __PREFS_COMMON_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +typedef struct _PrefsCommon PrefsCommon; + +#include "enums.h" +#include "prefs.h" + +typedef enum { + RECV_DIALOG_ALWAYS, + RECV_DIALOG_MANUAL, + RECV_DIALOG_NEVER +} RecvDialogMode; + +typedef enum { + CTE_AUTO, + CTE_BASE64, + CTE_QUOTED_PRINTABLE, + CTE_8BIT +} TransferEncodingMethod; + +typedef enum { + FENC_MIME, + FENC_RFC2231, + FENC_NONE +} MIMEFilenameEncodingMethod; + +struct _PrefsCommon +{ + /* Receive */ + gboolean scan_all_after_inc; + gboolean autochk_newmail; + gint autochk_itv; + gboolean chk_on_startup; + gboolean enable_newmsg_notify; + gchar *newmsg_notify_cmd; + + gboolean inc_local; + gboolean filter_on_inc; + gchar *spool_path; + + /* Send */ + gboolean savemsg; + gboolean filter_sent; + TransferEncodingMethod encoding_method; + MIMEFilenameEncodingMethod mime_fencoding_method; + + gboolean check_attach; + gchar *check_attach_str; + gboolean check_recipients; + gchar *check_recp_exclude; + + gboolean allow_jisx0201_kana; + + /* Compose */ + gboolean auto_sig; + gchar *sig_sep; + gint undolevels; + gint linewrap_len; + gboolean linewrap_quote; + gboolean autowrap; + gboolean auto_exteditor; + gboolean enable_autosave; + gint autosave_itv; + gboolean reply_account_autosel; + gboolean default_reply_list; + gboolean inherit_recipient_on_self_reply; + + gboolean show_ruler; + + /* Quote */ + gboolean reply_with_quote; + gchar *quotemark; + gchar *quotefmt; + gchar *fw_quotemark; + gchar *fw_quotefmt; + + /* Spelling */ + gboolean check_spell; + gchar *spell_lang; + + /* Display */ + gchar *textfont; + + gboolean trans_hdr; + gboolean display_folder_unread; + gboolean display_folder_num_columns; + gint ng_abbrev_len; + + gboolean swap_from; + gboolean expand_thread; + gchar *date_format; + + gboolean enable_rules_hint; + gboolean bold_unread; + + gboolean persist_qsearch_filter; + + ToolbarStyle toolbar_style; + gboolean show_searchbar; + gboolean show_statusbar; + + gchar *main_toolbar_setting; + gchar *compose_toolbar_setting; + + LayoutType layout_type; + + /* Summary columns visibility, position and size */ + gboolean summary_col_visible[N_SUMMARY_COLS]; + gint summary_col_pos[N_SUMMARY_COLS]; + gboolean summary_sent_col_visible[N_SUMMARY_COLS]; + gint summary_sent_col_pos[N_SUMMARY_COLS]; + gint summary_col_size[N_SUMMARY_COLS]; + + /* Widget visibility, position and size */ + gint folderwin_x; + gint folderwin_y; + gint folderview_width; + gint folderview_height; + gboolean folderview_visible; + + gint folder_col_folder; + gint folder_col_new; + gint folder_col_unread; + gint folder_col_total; + + gint summaryview_width; + gint summaryview_height; + gint summaryview_vwidth; + gint summaryview_vheight; + + gint main_msgwin_x; + gint main_msgwin_y; + gint msgview_width; + gint msgview_height; + gint msgview_vwidth; + gint msgview_vheight; + gboolean msgview_visible; + + gint mainview_x; + gint mainview_y; + gint mainview_width; + gint mainview_height; + gint mainwin_x; + gint mainwin_y; + gint mainwin_width; + gint mainwin_height; + + gint msgwin_width; + gint msgwin_height; + + gboolean mainwin_maximized; + + gint sourcewin_width; + gint sourcewin_height; + + gint compose_x; + gint compose_y; + gint compose_width; + gint compose_height; + + gboolean compose_maximized; + + gint addressbook_x; + gint addressbook_y; + gint addressbook_width; + gint addressbook_height; + + /* Message */ + gboolean enable_color; + gint quote_level1_col; + gint quote_level2_col; + gint quote_level3_col; + gint uri_col; + gushort sig_col; + gboolean recycle_quote_colors; + gboolean conv_mb_alnum; + gboolean display_header_pane; + gboolean display_header; + gint line_space; + gboolean render_html; + gboolean html_only_as_attach; + gboolean textview_cursor_visible; + gboolean enable_smooth_scroll; + gint scroll_step; + gboolean scroll_halfpage; + + gboolean resize_image; + gboolean inline_image; + + /* Encoding */ + gchar *force_charset; + gchar *default_encoding; + gchar *outgoing_charset; + + gboolean show_other_header; + GSList *disphdr_list; + + /* MIME viewer */ + gchar *mime_image_viewer; + gchar *mime_audio_player; + gchar *mime_open_cmd; + gchar *mime_cmd; + + GList *mime_open_cmd_history; + + /* Junk Mail */ + gboolean enable_junk; + gchar *junk_learncmd; + gchar *nojunk_learncmd; + gchar *junk_classify_cmd; + gchar *junk_folder; + gboolean filter_junk_on_recv; + gboolean filter_junk_before; + gboolean delete_junk_on_recv; + gboolean mark_junk_as_read; + + /* Privacy */ + gboolean auto_check_signatures; + gboolean gpg_signature_popup; + gboolean store_passphrase; + gint store_passphrase_timeout; + gboolean passphrase_grab; + gboolean gpg_warning; + + /* Interface */ + gboolean sep_folder; + gboolean sep_msg; + gboolean always_show_msg; + gboolean open_unread_on_enter; + gboolean remember_last_selected; + gboolean mark_as_read_on_new_window; + gboolean open_inbox_on_inc; + gboolean open_inbox_on_startup; + gboolean immediate_exec; + gboolean comply_gnome_hig; + gboolean show_trayicon; + gboolean minimize_to_tray; + gboolean toggle_window_on_trayicon_click; + + /* Other */ + RecvDialogMode recv_dialog_mode; + gboolean no_recv_err_panel; + gboolean close_recv_dialog; + + gboolean add_address_by_click; + + gboolean confirm_on_exit; + gboolean clean_on_exit; + gboolean ask_on_clean; + gboolean warn_queued_on_exit; + + gint logwin_line_limit; + + /* External commands */ + gchar *uri_cmd; + gchar *ext_editor_cmd; + + gboolean use_print_cmd; + gchar *print_cmd; + + gboolean use_extinc; + gchar *extinc_cmd; + gboolean use_extsend; + gchar *extsend_cmd; + + /* Update check */ + gboolean auto_update_check; + gboolean use_http_proxy; + gchar *http_proxy_host; + + /* Advanced */ + gboolean strict_cache_check; + gint io_timeout_secs; + + /* Filtering */ + GSList *fltlist; + + /* deprecated: do not use */ + GSList *junk_fltlist; + GSList *manual_junk_fltlist; + + /* Actions */ + GSList *actions_list; + + /* Online / Offline */ + gboolean online_mode; + + /* Append new members here */ + gboolean folder_col_visible[4]; /* Display */ + gboolean reply_address_only; /* Compose */ + gboolean recipients_autoreg; /* Send */ + gboolean enable_address_completion; /* Compose */ + gboolean fullauto_completion_mode; /* Compose */ + + gchar *user_agent_str; + + gboolean change_account_on_folder_sel; /* Interface */ + gboolean always_mark_read_on_show_msg; /* Interface */ + + gboolean always_add_address_only; /* Compose */ + gboolean show_send_dialog; /* Send */ + + gint addressbook_folder_width; + gint addressbook_col_name; + gint addressbook_col_addr; + gint addressbook_col_rem; + + gchar *prev_open_dir; + gchar *prev_save_dir; + gchar *prev_folder_dir; + + gboolean enable_newmsg_notify_sound; /* Receive */ + gchar *newmsg_notify_sound; /* Receive */ + + gboolean show_attach_tab; /* Message - Attachment */ + gboolean show_attached_files_first; /* Message - Attachment */ + gint attach_toolbutton_pos; /* Message - Attachment */ + + gboolean enable_newmsg_notify_window; /* Receive */ + + gboolean nofilter_junk_sender_in_book; /* Junk Mail */ + + gboolean alt_prefer_html; /* Message */ + + gint save_file_type; + + gint notify_window_period; /* Receive */ + + gint startup_online_mode; /* Online */ + + gint addressbook_col_nickname; +}; + +extern PrefsCommon prefs_common; + +PrefsCommon *prefs_common_get (void); + +PrefParam *prefs_common_get_params (void); + +void prefs_common_read_config (void); +void prefs_common_write_config (void); + +/* deprecated */ +void prefs_common_junk_filter_list_set (void); + +void prefs_common_junk_folder_rename_path (const gchar *old_path, + const gchar *new_path); + +#endif /* __PREFS_COMMON_H__ */ diff --git a/libsylph/procheader.c b/libsylph/procheader.c new file mode 100644 index 0000000..1dcffa2 --- /dev/null +++ b/libsylph/procheader.c @@ -0,0 +1,979 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2013 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "procheader.h" +#include "procmsg.h" +#include "codeconv.h" +#include "displayheader.h" +#include "prefs_common.h" +#include "utils.h" + +#define BUFFSIZE 8192 + +gint procheader_get_one_field(gchar *buf, size_t len, FILE *fp, + HeaderEntry hentry[]) +{ + gint nexthead; + gint hnum = 0; + HeaderEntry *hp = NULL; + + if (hentry != NULL) { + /* skip non-required headers */ + do { + do { + if (fgets(buf, len, fp) == NULL) + return -1; + if (buf[0] == '\r' || buf[0] == '\n') + return -1; + } while (buf[0] == ' ' || buf[0] == '\t'); + + for (hp = hentry, hnum = 0; hp->name != NULL; + hp++, hnum++) { + if (!g_ascii_strncasecmp(hp->name, buf, + strlen(hp->name))) + break; + } + } while (hp->name == NULL); + } else { + if (fgets(buf, len, fp) == NULL) return -1; + if (buf[0] == '\r' || buf[0] == '\n') return -1; + } + + /* unfold the specified folded line */ + if (hp && hp->unfold) { + gboolean folded = FALSE; + gchar *bufp = buf + strlen(buf); + + for (; bufp > buf && + (*(bufp - 1) == '\n' || *(bufp - 1) == '\r'); + bufp--) + *(bufp - 1) = '\0'; + + while (1) { + nexthead = fgetc(fp); + + /* folded */ + if (nexthead == ' ' || nexthead == '\t') + folded = TRUE; + else if (nexthead == EOF) + break; + else if (folded == TRUE) { + if ((len - (bufp - buf)) <= 2) break; + + if (nexthead == '\n') { + folded = FALSE; + continue; + } + + /* replace return code on the tail end + with space */ + *bufp++ = ' '; + *bufp++ = nexthead; + *bufp = '\0'; + + /* concatenate next line */ + if (fgets(bufp, len - (bufp - buf), fp) + == NULL) break; + bufp += strlen(bufp); + + for (; bufp > buf && + (*(bufp - 1) == '\n' || *(bufp - 1) == '\r'); + bufp--) + *(bufp - 1) = '\0'; + + folded = FALSE; + } else { + ungetc(nexthead, fp); + break; + } + } + + return hnum; + } + + while (1) { + nexthead = fgetc(fp); + if (nexthead == ' ' || nexthead == '\t') { + size_t buflen = strlen(buf); + + /* concatenate next line */ + if ((len - buflen) > 2) { + gchar *p = buf + buflen; + + *p++ = nexthead; + *p = '\0'; + buflen++; + if (fgets(p, len - buflen, fp) == NULL) + break; + } else + break; + } else { + if (nexthead != EOF) + ungetc(nexthead, fp); + break; + } + } + + /* remove trailing return code */ + strretchomp(buf); + + return hnum; +} + +gchar *procheader_get_unfolded_line(gchar *buf, size_t len, FILE *fp) +{ + gboolean folded = FALSE; + gint nexthead; + gchar *bufp; + + if (fgets(buf, len, fp) == NULL) return NULL; + if (buf[0] == '\r' || buf[0] == '\n') return NULL; + bufp = buf + strlen(buf); + + for (; bufp > buf && + (*(bufp - 1) == '\n' || *(bufp - 1) == '\r'); + bufp--) + *(bufp - 1) = '\0'; + + while (1) { + nexthead = fgetc(fp); + + /* folded */ + if (nexthead == ' ' || nexthead == '\t') + folded = TRUE; + else if (nexthead == EOF) + break; + else if (folded == TRUE) { + if ((len - (bufp - buf)) <= 2) break; + + if (nexthead == '\n') { + folded = FALSE; + continue; + } + + /* replace return code on the tail end + with space */ + *bufp++ = ' '; + *bufp++ = nexthead; + *bufp = '\0'; + + /* concatenate next line */ + if (fgets(bufp, len - (bufp - buf), fp) + == NULL) break; + bufp += strlen(bufp); + + for (; bufp > buf && + (*(bufp - 1) == '\n' || *(bufp - 1) == '\r'); + bufp--) + *(bufp - 1) = '\0'; + + folded = FALSE; + } else { + ungetc(nexthead, fp); + break; + } + } + + /* remove trailing return code */ + strretchomp(buf); + + return buf; +} + +GSList *procheader_get_header_list_from_file(const gchar *file) +{ + FILE *fp; + GSList *hlist; + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "procheader_get_header_list_from_file: fopen"); + return NULL; + } + + hlist = procheader_get_header_list(fp); + + fclose(fp); + return hlist; +} + +GSList *procheader_get_header_list(FILE *fp) +{ + gchar buf[BUFFSIZE]; + gchar *p; + GSList *hlist = NULL; + Header *header; + + g_return_val_if_fail(fp != NULL, NULL); + + while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) { + if (*buf == ':') continue; + for (p = buf; *p && *p != ' '; p++) { + if (*p == ':') { + header = g_new(Header, 1); + header->name = g_strndup(buf, p - buf); + p++; + while (*p == ' ' || *p == '\t') p++; + header->body = conv_unmime_header(p, NULL); + + hlist = g_slist_append(hlist, header); + break; + } + } + } + + return hlist; +} + +GSList *procheader_get_header_list_from_msginfo(MsgInfo *msginfo) +{ + GSList *hlist = NULL; + + g_return_val_if_fail(msginfo != NULL, NULL); + + if (msginfo->subject) + hlist = procheader_add_header_list(hlist, "Subject", + msginfo->subject); + if (msginfo->from) + hlist = procheader_add_header_list(hlist, "From", + msginfo->from); + if (msginfo->to) + hlist = procheader_add_header_list(hlist, "To", msginfo->to); + if (msginfo->cc) + hlist = procheader_add_header_list(hlist, "Cc", msginfo->cc); + if (msginfo->newsgroups) + hlist = procheader_add_header_list(hlist, "Newsgroups", + msginfo->newsgroups); + if (msginfo->date) + hlist = procheader_add_header_list(hlist, "Date", + msginfo->date); + + return hlist; +} + +GSList *procheader_add_header_list(GSList *hlist, const gchar *header_name, + const gchar *body) +{ + Header *header; + + g_return_val_if_fail(header_name != NULL, hlist); + + header = g_new(Header, 1); + header->name = g_strdup(header_name); + header->body = g_strdup(body); + + return g_slist_append(hlist, header); +} + +GSList *procheader_copy_header_list(GSList *hlist) +{ + GSList *newlist = NULL, *cur; + + for (cur = hlist; cur != NULL; cur = cur->next) { + Header *header = (Header *)cur->data; + newlist = procheader_add_header_list(newlist, header->name, + header->body); + } + + return newlist; +} + +GSList *procheader_merge_header_list(GSList *hlist1, GSList *hlist2) +{ + GSList *cur; + + for (cur = hlist2; cur != NULL; cur = cur->next) { + Header *header = (Header *)cur->data; + if (procheader_find_header_list(hlist1, header->name) < 0) + hlist1 = g_slist_append(hlist1, header); + } + + return hlist1; +} + +GSList *procheader_merge_header_list_dup(GSList *hlist1, GSList *hlist2) +{ + GSList *list, *cur; + + list = procheader_copy_header_list(hlist1); + + for (cur = hlist2; cur != NULL; cur = cur->next) { + Header *header = (Header *)cur->data; + if (procheader_find_header_list(list, header->name) < 0) + list = procheader_add_header_list(list, header->name, + header->body); + } + + return list; +} + +gint procheader_find_header_list(GSList *hlist, const gchar *header_name) +{ + GSList *cur; + gint index = 0; + Header *header; + + g_return_val_if_fail(header_name != NULL, -1); + + for (cur = hlist; cur != NULL; cur = cur->next, index++) { + header = (Header *)cur->data; + if (g_ascii_strcasecmp(header->name, header_name) == 0) + return index; + } + + return -1; +} + +GPtrArray *procheader_get_header_array(FILE *fp, const gchar *encoding) +{ + gchar buf[BUFFSIZE]; + gchar *p; + GPtrArray *headers; + Header *header; + + g_return_val_if_fail(fp != NULL, NULL); + + headers = g_ptr_array_new(); + + while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) { + if (*buf == ':') continue; + for (p = buf; *p && *p != ' '; p++) { + if (*p == ':') { + header = g_new(Header, 1); + header->name = g_strndup(buf, p - buf); + p++; + while (*p == ' ' || *p == '\t') p++; + header->body = conv_unmime_header(p, encoding); + + g_ptr_array_add(headers, header); + break; + } + } + } + + return headers; +} + +GPtrArray *procheader_get_header_array_asis(FILE *fp, const gchar *encoding) +{ + gchar buf[BUFFSIZE]; + gchar *p; + GPtrArray *headers; + Header *header; + + g_return_val_if_fail(fp != NULL, NULL); + + headers = g_ptr_array_new(); + + while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) { + if (*buf == ':') continue; + for (p = buf; *p && *p != ' '; p++) { + if (*p == ':') { + header = g_new(Header, 1); + header->name = g_strndup(buf, p - buf); + p++; + header->body = conv_unmime_header(p, encoding); + + g_ptr_array_add(headers, header); + break; + } + } + } + + return headers; +} + +GPtrArray *procheader_get_header_array_for_display(FILE *fp, + const gchar *encoding) +{ + GPtrArray *headers, *sorted_headers; + GSList *disphdr_list; + Header *header; + gint i; + + g_return_val_if_fail(fp != NULL, NULL); + + headers = procheader_get_header_array_asis(fp, encoding); + + sorted_headers = g_ptr_array_new(); + + for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL; + disphdr_list = disphdr_list->next) { + DisplayHeaderProp *dp = + (DisplayHeaderProp *)disphdr_list->data; + + for (i = 0; i < headers->len; i++) { + header = g_ptr_array_index(headers, i); + + if (!g_ascii_strcasecmp(header->name, dp->name)) { + if (dp->hidden) + procheader_header_free(header); + else + g_ptr_array_add(sorted_headers, header); + + g_ptr_array_remove_index(headers, i); + i--; + } + } + } + + if (prefs_common.show_other_header) { + for (i = 0; i < headers->len; i++) { + header = g_ptr_array_index(headers, i); + g_ptr_array_add(sorted_headers, header); + } + g_ptr_array_free(headers, TRUE); + } else + procheader_header_array_destroy(headers); + + return sorted_headers; +} + +void procheader_header_list_destroy(GSList *hlist) +{ + Header *header; + + while (hlist != NULL) { + header = hlist->data; + procheader_header_free(header); + hlist = g_slist_remove(hlist, header); + } +} + +void procheader_header_array_destroy(GPtrArray *harray) +{ + gint i; + Header *header; + + for (i = 0; i < harray->len; i++) { + header = g_ptr_array_index(harray, i); + procheader_header_free(header); + } + + g_ptr_array_free(harray, TRUE); +} + +void procheader_header_free(Header *header) +{ + if (!header) return; + + g_free(header->name); + g_free(header->body); + g_free(header); +} + +void procheader_get_header_fields(FILE *fp, HeaderEntry hentry[]) +{ + gchar buf[BUFFSIZE]; + HeaderEntry *hp; + gint hnum; + gchar *p; + + if (hentry == NULL) return; + + while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry)) + != -1) { + hp = hentry + hnum; + + p = buf + strlen(hp->name); + while (*p == ' ' || *p == '\t') p++; + + if (hp->body == NULL) + hp->body = g_strdup(p); + else if (!g_ascii_strcasecmp(hp->name, "To:") || + !g_ascii_strcasecmp(hp->name, "Cc:")) { + gchar *tp = hp->body; + hp->body = g_strconcat(tp, ", ", p, NULL); + g_free(tp); + } + } +} + +MsgInfo *procheader_parse_file(const gchar *file, MsgFlags flags, + gboolean full) +{ + GStatBuf s; + FILE *fp; + MsgInfo *msginfo; + + if (g_stat(file, &s) < 0) { + FILE_OP_ERROR(file, "stat"); + return NULL; + } + if (!S_ISREG(s.st_mode)) + return NULL; + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "procheader_parse_file: fopen"); + return NULL; + } + + msginfo = procheader_parse_stream(fp, flags, full); + fclose(fp); + + if (msginfo) { + msginfo->size = s.st_size; + msginfo->mtime = s.st_mtime; + } + + return msginfo; +} + +MsgInfo *procheader_parse_str(const gchar *str, MsgFlags flags, gboolean full) +{ + FILE *fp; + MsgInfo *msginfo; + + if ((fp = str_open_as_stream(str)) == NULL) + return NULL; + + msginfo = procheader_parse_stream(fp, flags, full); + fclose(fp); + return msginfo; +} + +enum +{ + H_DATE = 0, + H_FROM = 1, + H_TO = 2, + H_NEWSGROUPS = 3, + H_SUBJECT = 4, + H_MSG_ID = 5, + H_REFERENCES = 6, + H_IN_REPLY_TO = 7, + H_CONTENT_TYPE = 8, + H_SEEN = 9, + H_CC = 10, + H_X_FACE = 11 +}; + +MsgInfo *procheader_parse_stream(FILE *fp, MsgFlags flags, gboolean full) +{ + static HeaderEntry hentry_full[] = {{"Date:", NULL, FALSE}, + {"From:", NULL, TRUE}, + {"To:", NULL, TRUE}, + {"Newsgroups:", NULL, TRUE}, + {"Subject:", NULL, TRUE}, + {"Message-Id:", NULL, FALSE}, + {"References:", NULL, FALSE}, + {"In-Reply-To:", NULL, FALSE}, + {"Content-Type:", NULL, FALSE}, + {"Seen:", NULL, FALSE}, + {"Cc:", NULL, TRUE}, + {"X-Face:", NULL, FALSE}, + {NULL, NULL, FALSE}}; + + static HeaderEntry hentry_short[] = {{"Date:", NULL, FALSE}, + {"From:", NULL, TRUE}, + {"To:", NULL, TRUE}, + {"Newsgroups:", NULL, TRUE}, + {"Subject:", NULL, TRUE}, + {"Message-Id:", NULL, FALSE}, + {"References:", NULL, FALSE}, + {"In-Reply-To:", NULL, FALSE}, + {"Content-Type:", NULL, FALSE}, + {"Seen:", NULL, FALSE}, + {NULL, NULL, FALSE}}; + + MsgInfo *msginfo; + gchar buf[BUFFSIZE]; + gchar *p; + gchar *hp; + HeaderEntry *hentry; + gint hnum; + gchar *from = NULL, *to = NULL, *subject = NULL, *cc = NULL; + gchar *charset = NULL; + + hentry = full ? hentry_full : hentry_short; + + if (MSG_IS_QUEUED(flags)) { + while (fgets(buf, sizeof(buf), fp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + } + + msginfo = g_new0(MsgInfo, 1); + msginfo->flags = flags; + msginfo->references = NULL; + msginfo->inreplyto = NULL; + + while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry)) + != -1) { + hp = buf + strlen(hentry[hnum].name); + while (*hp == ' ' || *hp == '\t') hp++; + + switch (hnum) { + case H_DATE: + if (msginfo->date) break; + msginfo->date_t = + procheader_date_parse(NULL, hp, 0); + msginfo->date = g_strdup(hp); + break; + case H_FROM: + if (from) break; + from = g_strdup(hp); + break; + case H_TO: + if (to) { + p = to; + to = g_strconcat(p, ", ", hp, NULL); + g_free(p); + } else + to = g_strdup(hp); + break; + case H_NEWSGROUPS: + if (msginfo->newsgroups) { + p = msginfo->newsgroups; + msginfo->newsgroups = + g_strconcat(p, ",", hp, NULL); + g_free(p); + } else + msginfo->newsgroups = g_strdup(buf + 12); + break; + case H_SUBJECT: + if (msginfo->subject) break; + subject = g_strdup(hp); + break; + case H_MSG_ID: + if (msginfo->msgid) break; + + extract_parenthesis(hp, '<', '>'); + remove_space(hp); + msginfo->msgid = g_strdup(hp); + break; + case H_REFERENCES: + msginfo->references = + references_list_prepend(msginfo->references, + hp); + break; + case H_IN_REPLY_TO: + if (msginfo->inreplyto) break; + + eliminate_parenthesis(hp, '(', ')'); + if ((p = strrchr(hp, '<')) != NULL && + strchr(p + 1, '>') != NULL) { + extract_parenthesis(p, '<', '>'); + remove_space(p); + if (*p != '\0') + msginfo->inreplyto = g_strdup(p); + } + break; + case H_CONTENT_TYPE: + if (!g_ascii_strncasecmp(hp, "multipart", 9)) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_MIME); + } else { + if (!g_ascii_strncasecmp(hp, "text/html", 9)) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_MIME_HTML); + } + if (!charset) { + procmime_scan_content_type_str + (hp, NULL, &charset, NULL, NULL); + } + } + break; + case H_SEEN: + /* mnews Seen header */ + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_NEW|MSG_UNREAD); + break; + case H_CC: + if (cc) { + p = cc; + cc = g_strconcat(p, ", ", hp, NULL); + g_free(p); + } else + cc = g_strdup(hp); + break; + case H_X_FACE: + if (msginfo->xface) break; + msginfo->xface = g_strdup(hp); + break; + default: + break; + } + } + + if (from) { + msginfo->from = conv_unmime_header(from, charset); + subst_control(msginfo->from, ' '); + msginfo->fromname = procheader_get_fromname(msginfo->from); + g_free(from); + } + if (to) { + msginfo->to = conv_unmime_header(to, charset); + subst_control(msginfo->to, ' '); + g_free(to); + } + if (subject) { + msginfo->subject = conv_unmime_header(subject, charset); + subst_control(msginfo->subject, ' '); + g_free(subject); + } + if (cc) { + msginfo->cc = conv_unmime_header(cc, charset); + subst_control(msginfo->cc, ' '); + g_free(cc); + } + + if (!msginfo->inreplyto && msginfo->references) + msginfo->inreplyto = + g_strdup((gchar *)msginfo->references->data); + + if (MSG_IS_MIME(msginfo->flags)) { + MimeInfo *mimeinfo, *part; + gboolean has_html = FALSE; + + part = mimeinfo = procmime_scan_message_stream(fp); + while (part) { + if (part->mime_type != MIME_TEXT && + part->mime_type != MIME_TEXT_HTML && + part->mime_type != MIME_MULTIPART) + break; + if (part->mime_type == MIME_TEXT_HTML) + has_html = TRUE; + part = procmime_mimeinfo_next(part); + } + + if (has_html && !part) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_MIME_HTML); + } + + procmime_mimeinfo_free_all(mimeinfo); + } + + g_free(charset); + + return msginfo; +} + +gchar *procheader_get_fromname(const gchar *str) +{ + gchar *tmp, *name; + + tmp = g_strdup(str); + + if (*tmp == '\"') { + extract_quote_with_escape(tmp, '\"'); + g_strstrip(tmp); + } else if (strchr(tmp, '<')) { + eliminate_parenthesis(tmp, '<', '>'); + g_strstrip(tmp); + if (*tmp == '\0') { + strcpy(tmp, str); + extract_parenthesis(tmp, '<', '>'); + g_strstrip(tmp); + } + } else if (strchr(tmp, '(')) { + extract_parenthesis_with_escape(tmp, '(', ')'); + g_strstrip(tmp); + } + + if (*tmp == '\0') { + g_free(tmp); + name = g_strdup(str); + } else + name = tmp; + + return name; +} + +gchar *procheader_get_toname(const gchar *str) +{ + GSList *addr_list, *cur; + GString *toname; + gchar *name; + + if (strchr(str, ',') == NULL) + return procheader_get_fromname(str); + + addr_list = address_list_append_orig(NULL, str); + toname = g_string_new(NULL); + + for (cur = addr_list; cur != NULL; cur = cur->next) { + name = procheader_get_fromname((gchar *)cur->data); + g_string_append(toname, name); + g_free(name); + if (cur->next) + g_string_append(toname, ", "); + } + + slist_free_strings(addr_list); + + return g_string_free(toname, FALSE); +} + +static gint procheader_scan_date_string(const gchar *str, + gchar *weekday, gint *day, + gchar *month, gint *year, + gint *hh, gint *mm, gint *ss, + gchar *zone) +{ + gint result; + + *zone = '\0'; + result = sscanf(str, "%10s %d %9s %d %2d:%2d:%2d %5s", + weekday, day, month, year, hh, mm, ss, zone); + if (result >= 7) return 0; + + result = sscanf(str, "%3s,%d %9s %d %2d:%2d:%2d %5s", + weekday, day, month, year, hh, mm, ss, zone); + if (result >= 7) return 0; + + result = sscanf(str, "%3s,%d %9s %d %2d.%2d.%2d %5s", + weekday, day, month, year, hh, mm, ss, zone); + if (result >= 7) return 0; + + result = sscanf(str, "%3s %d, %9s %d %2d:%2d:%2d %5s", + weekday, day, month, year, hh, mm, ss, zone); + if (result >= 7) return 0; + + result = sscanf(str, "%d %9s %d %2d:%2d:%2d %5s", + day, month, year, hh, mm, ss, zone); + if (result >= 6) return 0; + + result = sscanf(str, "%d-%2s-%2d %2d:%2d:%2d", + year, month, day, hh, mm, ss); + if (result == 6) return 0; + + *ss = 0; + result = sscanf(str, "%10s %d %9s %d %2d:%2d %5s", + weekday, day, month, year, hh, mm, zone); + if (result >= 6) return 0; + + result = sscanf(str, "%d %9s %d %2d:%2d %5s", + day, month, year, hh, mm, zone); + if (result >= 5) return 0; + + return -1; +} + +stime_t procheader_date_parse(gchar *dest, const gchar *src, gint len) +{ + static gchar monthstr[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; + gchar weekday[11]; + gint day; + gchar month[10]; + gint year; + gint hh, mm, ss; + gchar zone[6]; + GDateMonth dmonth = G_DATE_BAD_MONTH; + struct tm t; + gchar *p; + time_t timer_; + time_t tz_offset; + stime_t timer; + + if (procheader_scan_date_string(src, weekday, &day, month, &year, + &hh, &mm, &ss, zone) < 0) { + g_warning("procheader_scan_date_string: date parse failed: %s", src); + if (dest && len > 0) + strncpy2(dest, src, len); + return 0; + } + + /* Y2K compliant :) */ + if (year < 1000) { + if (year < 50) + year += 2000; + else + year += 1900; + } + + month[3] = '\0'; + if (g_ascii_isdigit(month[0])) { + dmonth = atoi(month); + } else { + for (p = monthstr; *p != '\0'; p += 3) { + if (!g_ascii_strncasecmp(p, month, 3)) { + dmonth = (gint)(p - monthstr) / 3 + 1; + break; + } + } + } + + t.tm_sec = ss; + t.tm_min = mm; + t.tm_hour = hh; + t.tm_mday = day; + t.tm_mon = dmonth - 1; + t.tm_year = year - 1900; + t.tm_wday = 0; + t.tm_yday = 0; + t.tm_isdst = -1; + + timer_ = mktime(&t); + if (timer_ == -1) { + if (year >= 2038) { + g_warning("mktime: date overflow: %s", src); + timer_ = G_MAXINT - 12 * 3600; + } else { + g_warning("mktime: can't convert date: %s", src); + if (dest) + dest[0] = '\0'; + return 0; + } + } + + timer = timer_; + if (timer < G_MAXINT - 12 * 3600) { + tz_offset = remote_tzoffset_sec(zone); + if (tz_offset != -1) + timer += tzoffset_sec(&timer) - tz_offset; + } + + if (dest) + procheader_date_get_localtime(dest, len, timer); + + return timer; +} + +void procheader_date_get_localtime(gchar *dest, gint len, const stime_t timer) +{ + time_t timer_ = timer; + struct tm *lt; + gchar *default_format = "%y/%m/%d(%a) %H:%M"; + gchar *buf; + gchar tmp[BUFFSIZE]; + + lt = localtime(&timer_); + if (!lt) { + g_warning("can't get localtime of %ld\n", timer); + dest[0] = '\0'; + return; + } + + if (prefs_common.date_format) + strftime(tmp, sizeof(tmp), prefs_common.date_format, lt); + else + strftime(tmp, sizeof(tmp), default_format, lt); + + buf = conv_localetodisp(tmp, NULL); + strncpy2(dest, buf, len); + g_free(buf); +} diff --git a/libsylph/procheader.h b/libsylph/procheader.h new file mode 100644 index 0000000..fa8095a --- /dev/null +++ b/libsylph/procheader.h @@ -0,0 +1,101 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2007 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __PROCHEADER_H__ +#define __PROCHEADER_H__ + +#include +#include +#include + +#include "procmsg.h" + +typedef struct _HeaderEntry HeaderEntry; +typedef struct _Header Header; + +struct _HeaderEntry +{ + gchar *name; + gchar *body; + gboolean unfold; +}; + +struct _Header +{ + gchar *name; + gchar *body; +}; + +gint procheader_get_one_field (gchar *buf, + size_t len, + FILE *fp, + HeaderEntry hentry[]); +gchar *procheader_get_unfolded_line (gchar *buf, + size_t len, + FILE *fp); + +GSList *procheader_get_header_list_from_file (const gchar *file); +GSList *procheader_get_header_list (FILE *fp); +GSList *procheader_get_header_list_from_msginfo (MsgInfo *msginfo); +GSList *procheader_add_header_list (GSList *hlist, + const gchar *header_name, + const gchar *body); +GSList *procheader_copy_header_list (GSList *hlist); +GSList *procheader_merge_header_list (GSList *hlist1, + GSList *hlist2); +GSList *procheader_merge_header_list_dup (GSList *hlist1, + GSList *hlist2); +gint procheader_find_header_list (GSList *hlist, + const gchar *header_name); +void procheader_header_list_destroy (GSList *hlist); + +GPtrArray *procheader_get_header_array (FILE *fp, + const gchar *encoding); +GPtrArray *procheader_get_header_array_asis (FILE *fp, + const gchar *encoding); +GPtrArray *procheader_get_header_array_for_display + (FILE *fp, + const gchar *encoding); +void procheader_header_array_destroy (GPtrArray *harray); + +void procheader_header_free (Header *header); + +void procheader_get_header_fields (FILE *fp, + HeaderEntry hentry[]); +MsgInfo *procheader_parse_file (const gchar *file, + MsgFlags flags, + gboolean full); +MsgInfo *procheader_parse_str (const gchar *str, + MsgFlags flags, + gboolean full); +MsgInfo *procheader_parse_stream (FILE *fp, + MsgFlags flags, + gboolean full); + +gchar *procheader_get_fromname (const gchar *str); +gchar *procheader_get_toname (const gchar *str); + +stime_t procheader_date_parse (gchar *dest, + const gchar *src, + gint len); +void procheader_date_get_localtime (gchar *dest, + gint len, + const stime_t timer); + +#endif /* __PROCHEADER_H__ */ diff --git a/libsylph/procmime.c b/libsylph/procmime.c new file mode 100644 index 0000000..4c3a90c --- /dev/null +++ b/libsylph/procmime.c @@ -0,0 +1,1955 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2017 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include + +#include "procmime.h" +#include "procheader.h" +#include "base64.h" +#include "quoted-printable.h" +#include "uuencode.h" +#include "html.h" +#include "codeconv.h" +#include "utils.h" +#include "prefs_common.h" + +#undef MIME_DEBUG +/* #define MIME_DEBUG */ +#ifdef MIME_DEBUG +# define mime_debug_print debug_print +#else +# define mime_debug_print 1 ? (void)0 : debug_print +#endif + +#define MAX_MIME_LEVEL 64 + +static GHashTable *procmime_get_mime_type_table (void); +static GList *procmime_get_mime_type_list (const gchar *file); + + +MimeInfo *procmime_mimeinfo_new(void) +{ + MimeInfo *mimeinfo; + + mimeinfo = g_new0(MimeInfo, 1); + mimeinfo->mime_type = MIME_UNKNOWN; + mimeinfo->encoding_type = ENC_UNKNOWN; + + return mimeinfo; +} + +void procmime_mimeinfo_free_all(MimeInfo *mimeinfo) +{ + while (mimeinfo != NULL) { + MimeInfo *next; + + g_free(mimeinfo->encoding); + g_free(mimeinfo->content_type); + g_free(mimeinfo->charset); + g_free(mimeinfo->name); + g_free(mimeinfo->boundary); + g_free(mimeinfo->content_disposition); + g_free(mimeinfo->filename); + + g_free(mimeinfo->sigstatus); + g_free(mimeinfo->sigstatus_full); + + procmime_mimeinfo_free_all(mimeinfo->sub); + procmime_mimeinfo_free_all(mimeinfo->children); + procmime_mimeinfo_free_all(mimeinfo->plaintext); + + next = mimeinfo->next; + g_free(mimeinfo); + mimeinfo = next; + } +} + +MimeInfo *procmime_mimeinfo_insert(MimeInfo *parent, MimeInfo *mimeinfo) +{ + MimeInfo *child = parent->children; + + if (!child) + parent->children = mimeinfo; + else { + while (child->next != NULL) + child = child->next; + + child->next = mimeinfo; + } + + mimeinfo->parent = parent; + mimeinfo->level = parent->level + 1; + + return mimeinfo; +} + +#if 0 +void procmime_mimeinfo_replace(MimeInfo *old, MimeInfo *new) +{ + MimeInfo *parent = old->parent; + MimeInfo *child; + + g_return_if_fail(parent != NULL); + g_return_if_fail(new->next == NULL); + + for (child = parent->children; child && child != old; + child = child->next) + ; + if (!child) { + g_warning("oops: parent can't find it's own child"); + return; + } + procmime_mimeinfo_free_all(old); + + if (child == parent->children) { + new->next = parent->children->next; + parent->children = new; + } else { + new->next = child->next; + child = new; + } +} +#endif + +MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo) +{ + if (!mimeinfo) return NULL; + + if (mimeinfo->children) + return mimeinfo->children; + if (mimeinfo->sub) + return mimeinfo->sub; + if (mimeinfo->next) + return mimeinfo->next; + + if (mimeinfo->main) { + mimeinfo = mimeinfo->main; + if (mimeinfo->next) + return mimeinfo->next; + } + + for (mimeinfo = mimeinfo->parent; mimeinfo != NULL; + mimeinfo = mimeinfo->parent) { + if (mimeinfo->next) + return mimeinfo->next; + if (mimeinfo->main) { + mimeinfo = mimeinfo->main; + if (mimeinfo->next) + return mimeinfo->next; + } + } + + return NULL; +} + +#if 0 +void procmime_dump_mimeinfo(MimeInfo *mimeinfo) +{ + gint i; + + g_print("\n"); + + for (; mimeinfo != NULL; mimeinfo = procmime_mimeinfo_next(mimeinfo)) { + for (i = 0; i < mimeinfo->level; i++) + g_print(" "); + g_print("%s%s\n", mimeinfo->main ? "sub: " : "", + mimeinfo->content_type); + } +} +#endif + +MimeInfo *procmime_scan_message(MsgInfo *msginfo) +{ + FILE *fp; + MimeInfo *mimeinfo; + + g_return_val_if_fail(msginfo != NULL, NULL); + + if ((fp = procmsg_open_message_decrypted(msginfo, &mimeinfo)) == NULL) + return NULL; + + if (mimeinfo) { + mimeinfo->size = msginfo->size; + mimeinfo->content_size = get_left_file_size(fp); + if (mimeinfo->encoding_type == ENC_BASE64) + mimeinfo->content_size = mimeinfo->content_size / 4 * 3; + if (mimeinfo->mime_type == MIME_MULTIPART || + mimeinfo->mime_type == MIME_MESSAGE_RFC822) + procmime_scan_multipart_message(mimeinfo, fp); + } + + fclose(fp); + + return mimeinfo; +} + +MimeInfo *procmime_scan_message_stream(FILE *fp) +{ + MimeInfo *mimeinfo; + glong fpos; + + g_return_val_if_fail(fp != NULL, NULL); + + if (fseek(fp, 0L, SEEK_SET) < 0) { + FILE_OP_ERROR("procmime_scan_message_stream()", "fseek"); + return NULL; + } + + mimeinfo = procmime_scan_mime_header(fp); + + if (mimeinfo) { + fpos = ftell(fp); + mimeinfo->content_size = get_left_file_size(fp); + mimeinfo->size = fpos + mimeinfo->content_size; + if (mimeinfo->encoding_type == ENC_BASE64) + mimeinfo->content_size = mimeinfo->content_size / 4 * 3; + if (mimeinfo->mime_type == MIME_MULTIPART || + mimeinfo->mime_type == MIME_MESSAGE_RFC822) + procmime_scan_multipart_message(mimeinfo, fp); + } + + return mimeinfo; +} + +void procmime_scan_multipart_message(MimeInfo *mimeinfo, FILE *fp) +{ + gchar *p; + gchar *boundary; + gint boundary_len = 0; + gchar *buf; + glong fpos, prev_fpos; + + g_return_if_fail(mimeinfo != NULL); + g_return_if_fail(mimeinfo->mime_type == MIME_MULTIPART || + mimeinfo->mime_type == MIME_MESSAGE_RFC822); + + if (mimeinfo->mime_type == MIME_MULTIPART) { + g_return_if_fail(mimeinfo->boundary != NULL); + g_return_if_fail(mimeinfo->sub == NULL); + } + g_return_if_fail(fp != NULL); + + buf = g_malloc(BUFFSIZE); + + boundary = mimeinfo->boundary; + + if (boundary) { + boundary_len = strlen(boundary); + + /* look for first boundary */ + while ((p = fgets(buf, BUFFSIZE, fp)) != NULL) + if (IS_BOUNDARY(buf, boundary, boundary_len)) break; + if (!p) { + g_free(buf); + return; + } + } else if (mimeinfo->parent && mimeinfo->parent->boundary) { + boundary = mimeinfo->parent->boundary; + boundary_len = strlen(boundary); + } + + if ((fpos = ftell(fp)) < 0) { + perror("ftell"); + g_free(buf); + return; + } + + mime_debug_print("==== enter part\n"); + mime_debug_print("level = %d\n", mimeinfo->level); + + for (;;) { + MimeInfo *partinfo; + gboolean eom = FALSE; + glong content_pos; + gboolean is_base64; + gint len; + guint b64_content_len = 0; + gint b64_pad_len = 0; + + prev_fpos = fpos; + mime_debug_print("prev_fpos: %ld\n", fpos); + + /* scan part header */ + if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) { + MimeInfo *sub; + + mimeinfo->sub = sub = procmime_scan_mime_header(fp); + if (!sub) break; + + mime_debug_print("message/rfc822 part (content-type: %s)\n", + sub->content_type); + sub->level = mimeinfo->level + 1; + sub->parent = mimeinfo->parent; + sub->main = mimeinfo; + + partinfo = sub; + } else { + partinfo = procmime_scan_mime_header(fp); + if (!partinfo) break; + procmime_mimeinfo_insert(mimeinfo, partinfo); + mime_debug_print("content-type: %s\n", + partinfo->content_type); + if (partinfo->filename) + mime_debug_print("filename: %s\n", partinfo->filename); + else if (partinfo->name) + mime_debug_print("name: %s\n", partinfo->name); + } + + /* begin content */ + content_pos = ftell(fp); + mime_debug_print("content_pos: %ld\n", content_pos); + + if (partinfo->mime_type == MIME_MULTIPART || + partinfo->mime_type == MIME_MESSAGE_RFC822) { + if (partinfo->level < MAX_MIME_LEVEL) { + mime_debug_print("\n"); + mime_debug_print("enter to child part:\n"); + procmime_scan_multipart_message(partinfo, fp); + } + } + + /* look for next boundary */ + buf[0] = '\0'; + is_base64 = partinfo->encoding_type == ENC_BASE64; + while ((p = fgets(buf, BUFFSIZE, fp)) != NULL) { + if (IS_BOUNDARY(buf, boundary, boundary_len)) { + if (buf[2 + boundary_len] == '-' && + buf[2 + boundary_len + 1] == '-') + eom = TRUE; + break; + } else if (is_base64) { + const gchar *s; + for (s = buf; *s && *s != '\r' && *s != '\n'; + ++s) + if (*s == '=') + ++b64_pad_len; + b64_content_len += s - buf; + } + } + if (p == NULL) { + /* broken MIME, or single part MIME message */ + buf[0] = '\0'; + eom = TRUE; + } + mime_debug_print("boundary: %s\n", buf); + + fpos = ftell(fp); + mime_debug_print("fpos: %ld\n", fpos); + + len = strlen(buf); + partinfo->size = fpos - prev_fpos - len; + if (is_base64) + partinfo->content_size = + b64_content_len / 4 * 3 - b64_pad_len; + else + partinfo->content_size = fpos - content_pos - len; + mime_debug_print("partinfo->size: %d\n", partinfo->size); + mime_debug_print("partinfo->content_size: %d\n", + partinfo->content_size); + if (partinfo->sub && !partinfo->sub->sub && + !partinfo->sub->children) { + partinfo->sub->size = + fpos - partinfo->sub->fpos - strlen(buf); + mime_debug_print("partinfo->sub->size: %d\n", + partinfo->sub->size); + } + + if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) { + if (len > 0 && fseek(fp, fpos - len, SEEK_SET) < 0) + perror("fseek"); + break; + } + + if (eom) break; + } + + g_free(buf); + mime_debug_print("==== leave part\n"); +} + +void procmime_scan_encoding(MimeInfo *mimeinfo, const gchar *encoding) +{ + gchar *enc; + + g_free(mimeinfo->encoding); + enc = mimeinfo->encoding = g_strstrip(g_strdup(encoding)); + + if (!g_ascii_strncasecmp(enc, "7bit", 4)) + mimeinfo->encoding_type = ENC_7BIT; + else if (!g_ascii_strncasecmp(enc, "8bit", 4)) + mimeinfo->encoding_type = ENC_8BIT; + else if (!g_ascii_strncasecmp(enc, "quoted-printable", 16)) + mimeinfo->encoding_type = ENC_QUOTED_PRINTABLE; + else if (!g_ascii_strncasecmp(enc, "base64", 6)) + mimeinfo->encoding_type = ENC_BASE64; + else if (!g_ascii_strncasecmp(enc, "x-uuencode", 10)) + mimeinfo->encoding_type = ENC_X_UUENCODE; + else + mimeinfo->encoding_type = ENC_UNKNOWN; + +} + +void procmime_scan_content_type(MimeInfo *mimeinfo, const gchar *content_type) +{ + g_free(mimeinfo->content_type); + g_free(mimeinfo->charset); + g_free(mimeinfo->name); + g_free(mimeinfo->boundary); + mimeinfo->content_type = NULL; + mimeinfo->charset = NULL; + mimeinfo->name = NULL; + mimeinfo->boundary = NULL; + + procmime_scan_content_type_str(content_type, &mimeinfo->content_type, + &mimeinfo->charset, &mimeinfo->name, + &mimeinfo->boundary); + + mimeinfo->mime_type = procmime_scan_mime_type(mimeinfo->content_type); + if (mimeinfo->mime_type == MIME_MULTIPART && !mimeinfo->boundary) + mimeinfo->mime_type = MIME_TEXT; +} + +typedef struct +{ + gchar *name; + gchar *value; +} MimeParam; + +typedef struct +{ + gchar *hvalue; + GSList *plist; +} MimeParams; + +static gchar *procmime_find_parameter_delimiter(const gchar *param, + const gchar **eq) +{ + register const gchar *p = param; + gboolean quoted = FALSE; + const gchar *delim = NULL; + + while (*p) { + if (*p == '=') + break; + else if (*p == ';' || *p == '\r' || *p == '\n') { + delim = p; + break; + } + ++p; + } + if (*p != '=') { + *eq = NULL; + return (gchar *)delim; + } + *eq = p; + + ++p; + while (g_ascii_isspace(*p)) + ++p; + if (*p == '"') { + quoted = TRUE; + ++p; + } + + while (*p) { + if (quoted == TRUE) { + if (*p == '"') + quoted = FALSE; + } else if (*p == ';' || *p == '\r' || *p == '\n') { + delim = p; + break; + } + ++p; + } + + return (gchar *)delim; +} + +static gchar *procmime_convert_value(const gchar *value, const gchar *charset) +{ + if (charset) { + gchar *utf8_value; + + utf8_value = conv_codeset_strdup(value, charset, CS_INTERNAL); + if (utf8_value) + return utf8_value; + } + + return g_strdup(value); +} + +static MimeParams *procmime_parse_mime_parameter(const gchar *str) +{ + ConvADType ad_type; + gchar *tmp_param = NULL; + gchar *hvalue; + gchar *param, *name, *value; + gchar *charset = NULL, *lang = NULL; + const gchar *p, *delim; + gint count, prev_count; + gchar *cont_name; + gchar *cont_value; + MimeParam *mparam; + MimeParams *mparams; + GSList *plist = NULL; + + if ((p = strchr(str, ';'))) + hvalue = g_strndup(str, p - str); + else + hvalue = g_strdup(str); + + g_strstrip(hvalue); + + mparams = g_new(MimeParams, 1); + mparams->hvalue = hvalue; + mparams->plist = NULL; + + if (!p) + return mparams; + ++p; + + /* workaround for raw-JIS filename (Eudora etc.) */ + ad_type = conv_get_autodetect_type(); + if ((ad_type == C_AD_JAPANESE || + (ad_type == C_AD_BY_LOCALE && conv_is_ja_locale())) && + strstr(p, "\033$") != NULL) { + CodeConvFunc conv_func; + conv_func = conv_get_code_conv_func(NULL, NULL); + tmp_param = conv_func(p, NULL); + p = tmp_param; + debug_print("procmime_parse_mime_parameter(): raw-JIS header body detected: %s\n", str); + } + + count = prev_count = -1; + cont_name = cont_value = NULL; + + for (;;) { + gboolean encoded = FALSE; + gchar *begin; + gchar *dec_value; + const gchar *eq; + gchar *ast = NULL; + + while (*p == ';' || g_ascii_isspace(*p)) + ++p; + if (*p == '\0') + break; + + delim = procmime_find_parameter_delimiter(p, &eq); + if (!eq) + break; + if (delim) + param = g_strndup(p, delim - p); + else + param = g_strdup(p); + + name = g_strndup(p, eq - p); + g_strchomp(name); + if (*name != '*' && (ast = strchr(name, '*'))) { + const gchar *next = ast + 1; + + if (*next == '\0') { + encoded = TRUE; + } else if (g_ascii_isdigit(*next)) { + count = atoi(next); + while (g_ascii_isdigit(*next)) + ++next; + if (*next == '*') + encoded = TRUE; + if (prev_count + 1 != count) { + g_warning("procmime_parse_mime_parameter(): invalid count: %s\n", str); + g_free(name); + g_free(param); + break; + } + } else { + g_warning("procmime_parse_mime_parameter(): invalid name: %s\n", str); + g_free(name); + g_free(param); + break; + } + + *ast = '\0'; + } + + value = g_strdup(param + (eq - p) + 1); + g_strstrip(value); + if (*value == '"') + extract_quote(value, '"'); + + begin = value; + + if (encoded) { + gchar *sq1, *sq2; + + if ((sq1 = strchr(value, '\''))) { + if (sq1 > value) { + if (charset) + g_free(charset); + charset = g_strndup(value, sq1 - value); + } + if ((sq2 = strchr(sq1 + 1, '\''))) { + if (sq2 > sq1 + 1) { + if (lang) + g_free(lang); + lang = g_strndup(sq1 + 1, + sq2 - sq1 - 1); + } + begin = sq2 + 1; + } + } + } + +#define CONCAT_CONT_VALUE(s) \ +{ \ + if (cont_value) { \ + gchar *tmp; \ + tmp = g_strconcat(cont_value, s, NULL); \ + g_free(cont_value); \ + cont_value = tmp; \ + } else \ + cont_value = g_strdup(s); \ +} + + if (count >= 0) { + if (count > 0 && cont_name) { + if (strcmp(cont_name, name) != 0) { + g_warning("procmime_parse_mime_parameter(): mismatch parameter name: %s\n", str); + g_free(name); + g_free(value); + g_free(param); + break; + } + } else + cont_name = g_strdup(name); + + if (encoded) { + dec_value = g_malloc(strlen(begin) + 1); + decode_xdigit_encoded_str(dec_value, begin); + CONCAT_CONT_VALUE(dec_value); + g_free(dec_value); + } else { + CONCAT_CONT_VALUE(begin); + } + } + +#undef CONCAT_CONT_VALUE + + if (count == -1 && cont_name && cont_value) { + mparam = g_new(MimeParam, 1); + mparam->name = cont_name; + cont_name = NULL; + mparam->value = procmime_convert_value + (cont_value, charset); + g_free(cont_value); + cont_value = NULL; + plist = g_slist_prepend(plist, mparam); + } + + if (count == -1) { + mparam = g_new(MimeParam, 1); + mparam->name = name; + if (encoded) { + dec_value = g_malloc(strlen(begin) + 1); + decode_xdigit_encoded_str(dec_value, begin); + mparam->value = procmime_convert_value + (dec_value, charset); + g_free(dec_value); + } else { + if (!ast && + (!g_ascii_strcasecmp(name, "name") || + !g_ascii_strcasecmp(name, "filename"))) + mparam->value = + conv_unmime_header(begin, NULL); + else + mparam->value = g_strdup(begin); + } + name = NULL; + plist = g_slist_prepend(plist, mparam); + } + + g_free(name); + g_free(value); + g_free(param); + + prev_count = count; + count = -1; + + if (delim) + p = delim + 1; + else + break; + } + + if (cont_name && cont_value) { + mparam = g_new(MimeParam, 1); + mparam->name = cont_name; + cont_name = NULL; + mparam->value = procmime_convert_value(cont_value, charset); + plist = g_slist_prepend(plist, mparam); + } + + g_free(cont_name); + g_free(cont_value); + g_free(lang); + g_free(charset); + if (tmp_param) + g_free(tmp_param); + + plist = g_slist_reverse(plist); + mparams->plist = plist; + + return mparams; +} + +static void procmime_mime_params_free(MimeParams *mparams) +{ + GSList *cur; + + if (!mparams) + return; + + g_free(mparams->hvalue); + for (cur = mparams->plist; cur != NULL; cur = cur->next) { + MimeParam *mparam = (MimeParam *)cur->data; + g_free(mparam->name); + g_free(mparam->value); + g_free(mparam); + } + g_slist_free(mparams->plist); + g_free(mparams); +} + +void procmime_scan_content_type_str(const gchar *content_type, + gchar **mime_type, gchar **charset, + gchar **name, gchar **boundary) +{ + MimeParams *mparams; + GSList *cur; + + mparams = procmime_parse_mime_parameter(content_type); + + if (mime_type) + *mime_type = g_strdup(mparams->hvalue); + + for (cur = mparams->plist; cur != NULL; cur = cur->next) { + MimeParam *param = (MimeParam *)cur->data; + if (charset && !g_ascii_strcasecmp(param->name, "charset")) { + *charset = g_strdup(param->value); + eliminate_parenthesis(*charset, '(', ')'); + g_strstrip(*charset); + charset = NULL; + } else if (name && !g_ascii_strcasecmp(param->name, "name")) { + *name = g_strdup(param->value); + name = NULL; + } else if (boundary && + !g_ascii_strcasecmp(param->name, "boundary")) { + *boundary = g_strdup(param->value); + boundary = NULL; + } + } + + procmime_mime_params_free(mparams); +} + +void procmime_scan_content_type_partial(const gchar *content_type, + gint *total, gchar **part_id, + gint *number) +{ + MimeParams *mparams; + GSList *cur; + gchar *id_str = NULL; + gint t = 0, n = 0; + + *total = 0; + *part_id = NULL; + *number = 0; + + mparams = procmime_parse_mime_parameter(content_type); + + if (!mparams->hvalue || + g_ascii_strcasecmp(mparams->hvalue, "message/partial") != 0) { + procmime_mime_params_free(mparams); + return; + } + + for (cur = mparams->plist; cur != NULL; cur = cur->next) { + MimeParam *param = (MimeParam *)cur->data; + if (!g_ascii_strcasecmp(param->name, "total")) { + t = atoi(param->value); + } else if (!id_str && !g_ascii_strcasecmp(param->name, "id")) { + id_str = g_strdup(param->value); + } else if (!g_ascii_strcasecmp(param->name, "number")) { + n = atoi(param->value); + } + } + + procmime_mime_params_free(mparams); + + if (n > 0 && (t == 0 || t >= n) && id_str) { + *total = t; + *part_id = id_str; + *number = n; + } else { + g_free(id_str); + } +} + +void procmime_scan_content_disposition(MimeInfo *mimeinfo, + const gchar *content_disposition) +{ + MimeParams *mparams; + GSList *cur; + + mparams = procmime_parse_mime_parameter(content_disposition); + + mimeinfo->content_disposition = g_strdup(mparams->hvalue); + + for (cur = mparams->plist; cur != NULL; cur = cur->next) { + MimeParam *param = (MimeParam *)cur->data; + if (!g_ascii_strcasecmp(param->name, "filename")) { + mimeinfo->filename = g_strdup(param->value); + break; + } + } + + procmime_mime_params_free(mparams); +} + +enum +{ + H_CONTENT_TRANSFER_ENCODING = 0, + H_CONTENT_TYPE = 1, + H_CONTENT_DISPOSITION = 2 +}; + +MimeInfo *procmime_scan_mime_header(FILE *fp) +{ + static HeaderEntry hentry[] = {{"Content-Transfer-Encoding:", + NULL, FALSE}, + {"Content-Type:", NULL, TRUE}, + {"Content-Disposition:", + NULL, TRUE}, + {NULL, NULL, FALSE}}; + gchar buf[BUFFSIZE]; + gint hnum; + HeaderEntry *hp; + MimeInfo *mimeinfo; + + g_return_val_if_fail(fp != NULL, NULL); + + mimeinfo = procmime_mimeinfo_new(); + mimeinfo->mime_type = MIME_TEXT; + mimeinfo->encoding_type = ENC_7BIT; + mimeinfo->fpos = ftell(fp); + + while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry)) + != -1) { + hp = hentry + hnum; + + if (H_CONTENT_TRANSFER_ENCODING == hnum) { + procmime_scan_encoding + (mimeinfo, buf + strlen(hp->name)); + } else if (H_CONTENT_TYPE == hnum) { + procmime_scan_content_type + (mimeinfo, buf + strlen(hp->name)); + } else if (H_CONTENT_DISPOSITION == hnum) { + procmime_scan_content_disposition + (mimeinfo, buf + strlen(hp->name)); + } + } + + if (mimeinfo->mime_type == MIME_APPLICATION_OCTET_STREAM && + (mimeinfo->filename || mimeinfo->name)) { + const gchar *type; + type = procmime_get_mime_type + (mimeinfo->filename ? mimeinfo->filename + : mimeinfo->name); + if (type) + mimeinfo->mime_type = procmime_scan_mime_type(type); + } + + if (!mimeinfo->content_type) + mimeinfo->content_type = g_strdup("text/plain"); + + return mimeinfo; +} + +static gint procmime_normalize_lbreak(FILE *infp, FILE *outfp) +{ + gchar buf[BUFFSIZE]; + gint len; + + g_return_val_if_fail(infp != NULL, -1); + g_return_val_if_fail(outfp != NULL, -1); + + while (fgets(buf, sizeof(buf), infp) != NULL) { + len = strlen(buf); + if (len == sizeof(buf) - 1 && buf[len - 1] != '\n') { + if (buf[len - 1] == '\r') { + ungetc('\r', infp); + buf[len - 1] = '\0'; + } + fputs(buf, outfp); + continue; + } +#ifdef G_OS_WIN32 + strretchomp(buf); + fputs(buf, outfp); + fputs("\r\n", outfp); +#else + strcrchomp(buf); + fputs(buf, outfp); +#endif + } + + return 0; +} + +FILE *procmime_decode_content(FILE *outfp, FILE *infp, MimeInfo *mimeinfo) +{ + gchar buf[BUFFSIZE]; + gchar *boundary = NULL; + gint boundary_len = 0; + gboolean tmp_file = FALSE; + gboolean normalize_lbreak = FALSE; + ContentType content_type; + + g_return_val_if_fail(infp != NULL, NULL); + g_return_val_if_fail(mimeinfo != NULL, NULL); + + if (!outfp) { + outfp = my_tmpfile(); + if (!outfp) { + perror("tmpfile"); + return NULL; + } + tmp_file = TRUE; + } + + if (mimeinfo->parent && mimeinfo->parent->boundary) { + boundary = mimeinfo->parent->boundary; + boundary_len = strlen(boundary); + } + + content_type = procmime_scan_mime_type(mimeinfo->content_type); + if (content_type == MIME_TEXT || + content_type == MIME_TEXT_HTML) { + normalize_lbreak = TRUE; + } + + if (mimeinfo->encoding_type == ENC_QUOTED_PRINTABLE) { + FILE *tmpfp = outfp; + gchar prev_empty_line[3] = ""; + + if (normalize_lbreak) { + tmpfp = my_tmpfile(); + if (!tmpfp) { + perror("tmpfile"); + if (tmp_file) fclose(outfp); + return NULL; + } + } + + while (fgets(buf, sizeof(buf), infp) != NULL && + (!boundary || + !IS_BOUNDARY(buf, boundary, boundary_len))) { + gint len; + + if (prev_empty_line[0]) { + fputs(prev_empty_line, tmpfp); + prev_empty_line[0] = '\0'; + } + + if (buf[0] == '\n' || + (buf[0] == '\r' && buf[1] == '\n')) + strcpy(prev_empty_line, buf); + else { + len = qp_decode_line(buf); + fwrite(buf, len, 1, tmpfp); + } + } + if (!boundary && prev_empty_line[0]) + fputs(prev_empty_line, tmpfp); + + if (normalize_lbreak) { + if (fflush(tmpfp) == EOF) { + perror("fflush"); + fclose(tmpfp); + if (tmp_file) fclose(outfp); + return NULL; + } + rewind(tmpfp); + procmime_normalize_lbreak(tmpfp, outfp); + fclose(tmpfp); + } + } else if (mimeinfo->encoding_type == ENC_BASE64) { + gchar outbuf[BUFFSIZE]; + gint len; + Base64Decoder *decoder; + FILE *tmpfp = outfp; + + if (normalize_lbreak) { + tmpfp = my_tmpfile(); + if (!tmpfp) { + perror("tmpfile"); + if (tmp_file) fclose(outfp); + return NULL; + } + } + + decoder = base64_decoder_new(); + while (fgets(buf, sizeof(buf), infp) != NULL && + (!boundary || + !IS_BOUNDARY(buf, boundary, boundary_len))) { + len = base64_decoder_decode(decoder, buf, + (guchar *)outbuf); + if (len < 0) { + g_warning("Bad BASE64 content\n"); + break; + } + fwrite(outbuf, sizeof(gchar), len, tmpfp); + } + base64_decoder_free(decoder); + + if (normalize_lbreak) { + if (fflush(tmpfp) == EOF) { + perror("fflush"); + fclose(tmpfp); + if (tmp_file) fclose(outfp); + return NULL; + } + rewind(tmpfp); + procmime_normalize_lbreak(tmpfp, outfp); + fclose(tmpfp); + } + } else if (mimeinfo->encoding_type == ENC_X_UUENCODE) { + gchar outbuf[BUFFSIZE]; + gint len; + gboolean flag = FALSE; + + while (fgets(buf, sizeof(buf), infp) != NULL && + (!boundary || + !IS_BOUNDARY(buf, boundary, boundary_len))) { + if(!flag && strncmp(buf,"begin ", 6)) continue; + + if (flag) { + len = fromuutobits(outbuf, buf); + if (len <= 0) { + if (len < 0) + g_warning("Bad UUENCODE content(%d)\n", len); + break; + } + fwrite(outbuf, sizeof(gchar), len, outfp); + } else + flag = TRUE; + } + } else { + gchar prev_empty_line[3] = ""; + gint len; + gboolean cont_line = FALSE; + + while (fgets(buf, sizeof(buf), infp) != NULL && + (!boundary || + !IS_BOUNDARY(buf, boundary, boundary_len))) { + if (prev_empty_line[0]) { + fputs(prev_empty_line, outfp); + prev_empty_line[0] = '\0'; + } + + len = strlen(buf); + if (len == sizeof(buf) - 1 && + buf[len - 1] != '\n') { + if (buf[len - 1] == '\r') { + ungetc('\r', infp); + buf[len - 1] = '\0'; + } + fputs(buf, outfp); + cont_line = TRUE; + continue; + } + + if (normalize_lbreak) { +#ifdef G_OS_WIN32 + strretchomp(buf); + if (!cont_line && buf[0] == '\0') + strcpy(prev_empty_line, "\r\n"); + else { + fputs(buf, outfp); + fputs("\r\n", outfp); + } +#else + strcrchomp(buf); + if (!cont_line && buf[0] == '\n') + strcpy(prev_empty_line, "\n"); + else + fputs(buf, outfp); +#endif + } else { + if (!cont_line && + (buf[0] == '\n' || + (buf[0] == '\r' && buf[1] == '\n'))) + strcpy(prev_empty_line, buf); + else + fputs(buf, outfp); + } + + cont_line = FALSE; + } + if (!boundary && prev_empty_line[0]) + fputs(prev_empty_line, outfp); + } + + if (fflush(outfp) == EOF) + perror("fflush"); + if (ferror(outfp) != 0) { + g_warning("procmime_decode_content(): Can't write to temporary file\n"); + if (tmp_file) fclose(outfp); + return NULL; + } + + if (tmp_file) rewind(outfp); + return outfp; +} + +gint procmime_get_part(const gchar *outfile, const gchar *infile, + MimeInfo *mimeinfo) +{ + FILE *infp; + gint ret; + + g_return_val_if_fail(outfile != NULL, -1); + g_return_val_if_fail(infile != NULL, -1); + g_return_val_if_fail(mimeinfo != NULL, -1); + + if ((infp = g_fopen(infile, "rb")) == NULL) { + FILE_OP_ERROR(infile, "fopen"); + return -1; + } + ret = procmime_get_part_fp(outfile, infp, mimeinfo); + fclose(infp); + + return ret; +} + +gint procmime_get_part_fp(const gchar *outfile, FILE *infp, MimeInfo *mimeinfo) +{ + FILE *outfp; + gchar buf[BUFFSIZE]; + + g_return_val_if_fail(outfile != NULL, -1); + g_return_val_if_fail(infp != NULL, -1); + g_return_val_if_fail(mimeinfo != NULL, -1); + + if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) { + FILE_OP_ERROR("procmime_get_part_fp()", "fseek"); + return -1; + } + if ((outfp = g_fopen(outfile, "wb")) == NULL) { + FILE_OP_ERROR(outfile, "fopen"); + return -1; + } + + while (fgets(buf, sizeof(buf), infp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + + if (procmime_decode_content(outfp, infp, mimeinfo) == NULL) { + fclose(outfp); + g_unlink(outfile); + return -1; + } + + if (fclose(outfp) == EOF) { + FILE_OP_ERROR(outfile, "fclose"); + g_unlink(outfile); + return -1; + } + + return 0; +} + +FILE *procmime_get_part_fp_fp(FILE *outfp, FILE *infp, MimeInfo *mimeinfo) +{ + gchar buf[BUFFSIZE]; + + g_return_val_if_fail(infp != NULL, NULL); + g_return_val_if_fail(mimeinfo != NULL, NULL); + + if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) { + FILE_OP_ERROR("procmime_get_part_fp()", "fseek"); + return NULL; + } + + while (fgets(buf, sizeof(buf), infp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + + if ((outfp = procmime_decode_content(outfp, infp, mimeinfo)) == NULL) { + return NULL; + } + + return outfp; +} + +gint procmime_get_all_parts(const gchar *dir, const gchar *infile, + MimeInfo *mimeinfo) +{ + FILE *fp; + MimeInfo *partinfo; + gchar *base, *filename; + + g_return_val_if_fail(dir != NULL, -1); + g_return_val_if_fail(infile != NULL, -1); + g_return_val_if_fail(mimeinfo != NULL, -1); + + if (!is_dir_exist(dir)) { + g_warning("%s: directory not exist.\n", dir); + return -1; + } + + if ((fp = g_fopen(infile, "rb")) == NULL) { + FILE_OP_ERROR(infile, "fopen"); + return -1; + } + + for (partinfo = mimeinfo; partinfo != NULL; + partinfo = procmime_mimeinfo_next(partinfo)) { + if (partinfo->filename || partinfo->name) { + gint count = 1; + + base = procmime_get_part_file_name(partinfo); + filename = g_strconcat(dir, G_DIR_SEPARATOR_S, base, + NULL); + + while (is_file_entry_exist(filename)) { + gchar *base_alt; + + base_alt = get_alt_filename(base, count++); + g_free(filename); + filename = g_strconcat + (dir, G_DIR_SEPARATOR_S, base_alt, + NULL); + g_free(base_alt); + } + + procmime_get_part_fp(filename, fp, partinfo); + + g_free(filename); + g_free(base); + } + } + + fclose(fp); + + return 0; +} + +FILE *procmime_get_text_content(MimeInfo *mimeinfo, FILE *infp, + const gchar *encoding) +{ + FILE *tmpfp, *outfp; + const gchar *src_encoding; + gboolean conv_fail = FALSE; + gchar buf[BUFFSIZE]; + + g_return_val_if_fail(mimeinfo != NULL, NULL); + g_return_val_if_fail(infp != NULL, NULL); + g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT || + mimeinfo->mime_type == MIME_TEXT_HTML, NULL); + + if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) { + perror("fseek"); + return NULL; + } + + while (fgets(buf, sizeof(buf), infp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + + tmpfp = procmime_decode_content(NULL, infp, mimeinfo); + if (!tmpfp) + return NULL; + + if ((outfp = my_tmpfile()) == NULL) { + perror("tmpfile"); + fclose(tmpfp); + return NULL; + } + + src_encoding = prefs_common.force_charset ? prefs_common.force_charset + : mimeinfo->charset ? mimeinfo->charset + : prefs_common.default_encoding; + + if (mimeinfo->mime_type == MIME_TEXT) { + while (fgets(buf, sizeof(buf), tmpfp) != NULL) { + gchar *str; + + str = conv_codeset_strdup(buf, src_encoding, encoding); + if (str) { + fputs(str, outfp); + g_free(str); + } else { + conv_fail = TRUE; + fputs(buf, outfp); + } + } + } else if (mimeinfo->mime_type == MIME_TEXT_HTML) { + HTMLParser *parser; + CodeConverter *conv; + const gchar *str; + + conv = conv_code_converter_new(src_encoding, encoding); + parser = html_parser_new(tmpfp, conv); + while ((str = html_parse(parser)) != NULL) { + fputs(str, outfp); + } + html_parser_destroy(parser); + conv_code_converter_destroy(conv); + } + + if (conv_fail) + g_warning(_("procmime_get_text_content(): Code conversion failed.\n")); + + fclose(tmpfp); + if (fflush(outfp) == EOF) { + perror("fflush"); + fclose(outfp); + return NULL; + } + rewind(outfp); + + return outfp; +} + +/* search the first text part of (multipart) MIME message, + decode, convert it and output to outfp. */ +FILE *procmime_get_first_text_content(MsgInfo *msginfo, const gchar *encoding) +{ + FILE *infp, *outfp = NULL; + MimeInfo *mimeinfo, *partinfo; + + g_return_val_if_fail(msginfo != NULL, NULL); + + mimeinfo = procmime_scan_message(msginfo); + if (!mimeinfo) return NULL; + + if ((infp = procmsg_open_message(msginfo)) == NULL) { + procmime_mimeinfo_free_all(mimeinfo); + return NULL; + } + + partinfo = mimeinfo; + while (partinfo && partinfo->mime_type != MIME_TEXT) + partinfo = procmime_mimeinfo_next(partinfo); + if (!partinfo) { + partinfo = mimeinfo; + while (partinfo && partinfo->mime_type != MIME_TEXT_HTML) + partinfo = procmime_mimeinfo_next(partinfo); + } + + if (partinfo) + outfp = procmime_get_text_content(partinfo, infp, encoding); + + fclose(infp); + procmime_mimeinfo_free_all(mimeinfo); + + return outfp; +} + +gboolean procmime_find_string_part(MimeInfo *mimeinfo, const gchar *filename, + const gchar *str, StrFindFunc find_func) +{ + + FILE *infp, *outfp; + gchar buf[BUFFSIZE]; + + g_return_val_if_fail(mimeinfo != NULL, FALSE); + g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT || + mimeinfo->mime_type == MIME_TEXT_HTML, FALSE); + g_return_val_if_fail(str != NULL, FALSE); + g_return_val_if_fail(find_func != NULL, FALSE); + + if ((infp = g_fopen(filename, "rb")) == NULL) { + FILE_OP_ERROR(filename, "fopen"); + return FALSE; + } + + outfp = procmime_get_text_content(mimeinfo, infp, NULL); + fclose(infp); + + if (!outfp) + return FALSE; + + while (fgets(buf, sizeof(buf), outfp) != NULL) { + strretchomp(buf); + if (find_func(buf, str)) { + fclose(outfp); + return TRUE; + } + } + + fclose(outfp); + + return FALSE; +} + +gboolean procmime_find_string(MsgInfo *msginfo, const gchar *str, + StrFindFunc find_func) +{ + MimeInfo *mimeinfo; + MimeInfo *partinfo; + gchar *filename; + gboolean found = FALSE; + + g_return_val_if_fail(msginfo != NULL, FALSE); + g_return_val_if_fail(str != NULL, FALSE); + g_return_val_if_fail(find_func != NULL, FALSE); + + filename = procmsg_get_message_file(msginfo); + if (!filename) return FALSE; + mimeinfo = procmime_scan_message(msginfo); + + for (partinfo = mimeinfo; partinfo != NULL; + partinfo = procmime_mimeinfo_next(partinfo)) { + if (partinfo->mime_type == MIME_TEXT || + partinfo->mime_type == MIME_TEXT_HTML) { + if (procmime_find_string_part + (partinfo, filename, str, find_func) == TRUE) { + found = TRUE; + break; + } + } + } + + procmime_mimeinfo_free_all(mimeinfo); + g_free(filename); + + return found; +} + +gchar *procmime_get_part_file_name(MimeInfo *mimeinfo) +{ + gchar *base; + const gchar *base_; + + base_ = mimeinfo->filename ? mimeinfo->filename + : mimeinfo->name ? mimeinfo->name : "mimetmp"; + base_ = g_basename(base_); + if (*base_ == '\0') base_ = "mimetmp"; + base = conv_filename_from_utf8(base_); + subst_for_filename(base); + + return base; +} + +gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo) +{ + static guint32 id = 0; + gchar *base; + gchar *filename = NULL; + gchar f_prefix[10]; + + g_return_val_if_fail(mimeinfo != NULL, NULL); + + if (MIME_TEXT_HTML == mimeinfo->mime_type) + base = g_strdup("mimetmp.html"); + else + base = procmime_get_part_file_name(mimeinfo); + + do { + g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++); + if (filename) + g_free(filename); + filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S, + f_prefix, base, NULL); + } while (is_file_entry_exist(filename)); + + g_free(base); + + debug_print("procmime_get_tmp_file_name: %s\n", filename); + + return filename; +} + +gchar *procmime_get_tmp_file_name_for_user(MimeInfo *mimeinfo) +{ + gchar *base; + gchar *filename; + gint count = 1; + + g_return_val_if_fail(mimeinfo != NULL, NULL); + + if (MIME_TEXT_HTML == mimeinfo->mime_type) + base = g_strdup("mimetmp.html"); + else + base = procmime_get_part_file_name(mimeinfo); + + filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S, + base, NULL); + while (is_file_entry_exist(filename)) { + gchar *base_alt; + + base_alt = get_alt_filename(base, count++); + g_free(filename); + filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S, + base_alt, NULL); + g_free(base_alt); + } + + g_free(base); + + debug_print("procmime_get_tmp_file_name_for_user: %s\n", filename); + + return filename; +} + +ContentType procmime_scan_mime_type(const gchar *mime_type) +{ + ContentType type; + + if (!g_ascii_strncasecmp(mime_type, "text/html", 9)) + type = MIME_TEXT_HTML; + else if (!g_ascii_strncasecmp(mime_type, "text/", 5)) + type = MIME_TEXT; + else if (!g_ascii_strncasecmp(mime_type, "message/rfc822", 14)) + type = MIME_MESSAGE_RFC822; + else if (!g_ascii_strncasecmp(mime_type, "message/", 8)) + type = MIME_TEXT; + else if (!g_ascii_strncasecmp(mime_type, "application/octet-stream", + 24)) + type = MIME_APPLICATION_OCTET_STREAM; + else if (!g_ascii_strncasecmp(mime_type, "application/", 12)) + type = MIME_APPLICATION; + else if (!g_ascii_strncasecmp(mime_type, "multipart/", 10)) + type = MIME_MULTIPART; + else if (!g_ascii_strncasecmp(mime_type, "image/", 6)) + type = MIME_IMAGE; + else if (!g_ascii_strncasecmp(mime_type, "audio/", 6)) + type = MIME_AUDIO; + else if (!g_ascii_strncasecmp(mime_type, "video/", 6)) + type = MIME_VIDEO; + else if (!g_ascii_strcasecmp(mime_type, "text")) + type = MIME_TEXT; + else + type = MIME_UNKNOWN; + + return type; +} + +static GList *mime_type_list = NULL; + +gchar *procmime_get_mime_type(const gchar *filename) +{ + static GHashTable *mime_type_table = NULL; + MimeType *mime_type; + const gchar *p; + gchar ext[64]; + static gboolean no_mime_type_table = FALSE; + + if (no_mime_type_table) + return NULL; + + if (!mime_type_table) { + mime_type_table = procmime_get_mime_type_table(); + if (!mime_type_table) { + no_mime_type_table = TRUE; + return NULL; + } + } + + filename = g_basename(filename); + p = strrchr(filename, '.'); + if (!p) return NULL; + + strncpy2(ext, p + 1, sizeof(ext)); + g_strdown(ext); + mime_type = g_hash_table_lookup(mime_type_table, ext); + if (mime_type) { + gchar *str; + + str = g_strconcat(mime_type->type, "/", mime_type->sub_type, + NULL); + return str; + } + + return NULL; +} + +static GHashTable *procmime_get_mime_type_table(void) +{ + GHashTable *table = NULL; + GList *cur; + MimeType *mime_type; + gchar **exts; + + if (!mime_type_list) { + GList *list; + gchar *dir; + +#ifdef G_OS_WIN32 + dir = g_strconcat(get_startup_dir(), + G_DIR_SEPARATOR_S "etc" G_DIR_SEPARATOR_S + "mime.types", NULL); + mime_type_list = procmime_get_mime_type_list(dir); + g_free(dir); +#else + mime_type_list = + procmime_get_mime_type_list(SYSCONFDIR "/mime.types"); + if (!mime_type_list) + mime_type_list = + procmime_get_mime_type_list("/etc/mime.types"); +#endif + dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + "mime.types", NULL); + list = procmime_get_mime_type_list(dir); + g_free(dir); + mime_type_list = g_list_concat(mime_type_list, list); + + if (!mime_type_list) { + debug_print("mime.types not found\n"); + return NULL; + } + } + + table = g_hash_table_new(g_str_hash, g_str_equal); + + for (cur = mime_type_list; cur != NULL; cur = cur->next) { + gint i; + gchar *key; + + mime_type = (MimeType *)cur->data; + + if (!mime_type->extension) continue; + + exts = g_strsplit(mime_type->extension, " ", 16); + for (i = 0; exts[i] != NULL; i++) { + /* make the key case insensitive */ + g_strdown(exts[i]); + /* use previously dup'd key on overwriting */ + if (g_hash_table_lookup(table, exts[i])) + key = exts[i]; + else + key = g_strdup(exts[i]); + g_hash_table_insert(table, key, mime_type); + } + g_strfreev(exts); + } + + return table; +} + +static GList *procmime_get_mime_type_list(const gchar *file) +{ + GList *list = NULL; + FILE *fp; + gchar buf[BUFFSIZE]; + gchar *p; + gchar *delim; + MimeType *mime_type; + + if ((fp = g_fopen(file, "rb")) == NULL) return NULL; + + debug_print("Reading %s ...\n", file); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + p = strchr(buf, '#'); + if (p) *p = '\0'; + g_strstrip(buf); + + p = buf; + while (*p && !g_ascii_isspace(*p)) p++; + if (*p) { + *p = '\0'; + p++; + } + delim = strchr(buf, '/'); + if (delim == NULL) continue; + *delim = '\0'; + + mime_type = g_new(MimeType, 1); + mime_type->type = g_strdup(buf); + mime_type->sub_type = g_strdup(delim + 1); + + while (*p && g_ascii_isspace(*p)) p++; + if (*p) + mime_type->extension = g_strdup(p); + else + mime_type->extension = NULL; + + list = g_list_append(list, mime_type); + } + + fclose(fp); + + if (!list) + g_warning("Can't read mime.types\n"); + + return list; +} + +static GList *mailcap_list = NULL; + +static GList *procmime_parse_mailcap(const gchar *file) +{ + GList *list = NULL; + FILE *fp; + gchar buf[BUFFSIZE]; + MailCap *mailcap; + + if ((fp = g_fopen(file, "rb")) == NULL) return NULL; + + while (fgets(buf, sizeof(buf), fp) != NULL) { + gint i; + gchar *p; + gchar **strv; + + p = strchr(buf, '#'); + if (p) *p = '\0'; + g_strstrip(buf); + + strv = strsplit_with_quote(buf, ";", 0); + if (!strv) + continue; + + for (i = 0; strv[i] != NULL; ++i) + g_strstrip(strv[i]); + + if (!strv[0] || *strv[0] == '\0' || + !strv[1] || *strv[1] == '\0') { + g_strfreev(strv); + continue; + } + + mailcap = g_new(MailCap, 1); + mailcap->mime_type = g_strdup(strv[0]); + mailcap->cmdline_fmt = g_strdup(strv[1]); + mailcap->needs_terminal = FALSE; + + for (i = 0; strv[i] != NULL; ++i) { + if (strcmp(strv[i], "needsterminal") == 0) + mailcap->needs_terminal = TRUE; + } + + g_strfreev(strv); + + list = g_list_append(list, mailcap); + } + + fclose(fp); + + return list; +} + +gint procmime_execute_open_file(const gchar *file, const gchar *mime_type) +{ + gchar *mime_type_ = NULL; + GList *cur; + MailCap *mailcap; + gchar *cmdline; + gint ret = -1; + static gboolean mailcap_list_init = FALSE; + + g_return_val_if_fail(file != NULL, -1); + + if (!mime_type || + g_ascii_strcasecmp(mime_type, "application/octet-stream") == 0) { + gchar *tmp; + tmp = procmime_get_mime_type(file); + if (!tmp) + return -1; + mime_type_ = g_ascii_strdown(tmp, -1); + g_free(tmp); + } else + mime_type_ = g_ascii_strdown(mime_type, -1); + + if (!mailcap_list_init && !mailcap_list) { + GList *list; + gchar *path; + + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "mailcap", + NULL); + mailcap_list = procmime_parse_mailcap(path); + g_free(path); +#ifdef G_OS_WIN32 + path = g_strconcat(get_startup_dir(), G_DIR_SEPARATOR_S "etc" + G_DIR_SEPARATOR_S "mailcap", NULL); + list = procmime_parse_mailcap(path); + g_free(path); +#else + if (!mailcap_list) { + path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, + ".mailcap", NULL); + mailcap_list = procmime_parse_mailcap(path); + g_free(path); + } + list = procmime_parse_mailcap(SYSCONFDIR "/mailcap"); + if (!list) + list = procmime_parse_mailcap("/etc/mailcap"); +#endif + mailcap_list = g_list_concat(mailcap_list, list); + + mailcap_list_init = TRUE; + } + + for (cur = mailcap_list; cur != NULL; cur = cur->next) { + mailcap = (MailCap *)cur->data; + + if (!g_pattern_match_simple(mailcap->mime_type, mime_type_)) + continue; + if (mailcap->needs_terminal) + continue; + + if (str_find_format_times(mailcap->cmdline_fmt, 's') == 1) + cmdline = g_strdup_printf(mailcap->cmdline_fmt, file); + else + cmdline = g_strconcat(mailcap->cmdline_fmt, " \"", file, + "\"", NULL); + ret = execute_command_line(cmdline, TRUE); + g_free(cmdline); + break; + } + + g_free(mime_type_); + + return ret; +} + +EncodingType procmime_get_encoding_for_charset(const gchar *charset) +{ + if (!charset) + return ENC_8BIT; + else if (!g_ascii_strncasecmp(charset, "ISO-2022-", 9) || + !g_ascii_strcasecmp(charset, "US-ASCII")) + return ENC_7BIT; + else if (!g_ascii_strcasecmp(charset, "ISO-8859-5") || + !g_ascii_strncasecmp(charset, "KOI8-", 5) || + !g_ascii_strcasecmp(charset, "Windows-1251")) + return ENC_8BIT; + else if (!g_ascii_strncasecmp(charset, "ISO-8859-", 9)) + return ENC_QUOTED_PRINTABLE; + else + return ENC_8BIT; +} + +EncodingType procmime_get_encoding_for_text_file(const gchar *file) +{ + FILE *fp; + guchar buf[BUFFSIZE]; + size_t len; + size_t octet_chars = 0; + size_t total_len = 0; + gfloat octet_percentage; + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return ENC_UNKNOWN; + } + + while ((len = fread(buf, sizeof(guchar), sizeof(buf), fp)) > 0) { + guchar *p; + gint i; + + for (p = buf, i = 0; i < len; ++p, ++i) { + if ((*p & 0x80) || *p == 0x00 || *p == 0x7f) { + ++octet_chars; + } + } + total_len += len; + } + + fclose(fp); + + if (total_len > 0) + octet_percentage = (gfloat)octet_chars / (gfloat)total_len; + else + octet_percentage = 0.0; + + debug_print("procmime_get_encoding_for_text_file(): " + "8bit chars: %d / %d (%f%%)\n", octet_chars, total_len, + 100.0 * octet_percentage); + + if (octet_percentage > 0.20) { + debug_print("using BASE64\n"); + return ENC_BASE64; + } else if (octet_chars > 0) { + debug_print("using quoted-printable\n"); + return ENC_QUOTED_PRINTABLE; + } else { + debug_print("using 7bit\n"); + return ENC_7BIT; + } +} + +EncodingType procmime_get_encoding_for_str(const gchar *str) +{ + const guchar *p; + size_t octet_chars = 0; + size_t total_len = 0; + gfloat octet_percentage; + + total_len = strlen(str); + + for (p = (const guchar *)str; *p != '\0'; ++p) { + if ((*p & 0x80) || *p == 0x7f) { + ++octet_chars; + } + } + + if (total_len > 0) + octet_percentage = (gfloat)octet_chars / (gfloat)total_len; + else + octet_percentage = 0.0; + + debug_print("procmime_get_encoding_for_str(): " + "8bit chars: %d / %d (%f%%)\n", octet_chars, total_len, + 100.0 * octet_percentage); + + if (octet_percentage > 0.20) { + debug_print("using BASE64\n"); + return ENC_BASE64; + } else if (octet_chars > 0) { + debug_print("using quoted-printable\n"); + return ENC_QUOTED_PRINTABLE; + } else { + debug_print("using 7bit\n"); + return ENC_7BIT; + } +} + +const gchar *procmime_get_encoding_str(EncodingType encoding) +{ + static const gchar *encoding_str[] = { + "7bit", "8bit", "quoted-printable", "base64", "x-uuencode", + NULL + }; + + if (encoding >= ENC_7BIT && encoding <= ENC_UNKNOWN) + return encoding_str[encoding]; + else + return NULL; +} diff --git a/libsylph/procmime.h b/libsylph/procmime.h new file mode 100644 index 0000000..0242d34 --- /dev/null +++ b/libsylph/procmime.h @@ -0,0 +1,213 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2010 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __PROCMIME_H__ +#define __PROCMIME_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +typedef struct _MimeType MimeType; +typedef struct _MailCap MailCap; +typedef struct _MimeInfo MimeInfo; + +#include "procmsg.h" +#include "utils.h" + +typedef enum +{ + ENC_7BIT, + ENC_8BIT, + ENC_QUOTED_PRINTABLE, + ENC_BASE64, + ENC_X_UUENCODE, + ENC_UNKNOWN +} EncodingType; + +typedef enum +{ + MIME_TEXT, + MIME_TEXT_HTML, + MIME_MESSAGE_RFC822, + MIME_APPLICATION, + MIME_APPLICATION_OCTET_STREAM, + MIME_MULTIPART, + MIME_IMAGE, + MIME_AUDIO, + MIME_VIDEO, + MIME_UNKNOWN +} ContentType; + +struct _MimeType +{ + gchar *type; + gchar *sub_type; + + gchar *extension; +}; + +struct _MailCap +{ + gchar *mime_type; + gchar *cmdline_fmt; + gboolean needs_terminal; +}; + +/* + * An example of MimeInfo structure: + * + * multipart/mixed root <-+ parent + * | + * multipart/alternative children <-+ parent + * | + * text/plain children --+ + * | + * text/html next <-+ + * + * message/rfc822 next <-+ main + * | + * sub (capsulated message) + * + * image/jpeg next + */ + +struct _MimeInfo +{ + gchar *encoding; + + EncodingType encoding_type; + ContentType mime_type; + + gchar *content_type; + gchar *charset; + gchar *name; + gchar *boundary; + + gchar *content_disposition; + gchar *filename; + + glong fpos; + guint size; + guint content_size; + + MimeInfo *main; + MimeInfo *sub; + + MimeInfo *next; + MimeInfo *parent; + MimeInfo *children; + + MimeInfo *plaintext; + gchar *sigstatus; + gchar *sigstatus_full; + + gint level; +}; + +#define IS_BOUNDARY(s, bnd, len) \ + (bnd && s[0] == '-' && s[1] == '-' && !strncmp(s + 2, bnd, len)) + +/* MimeInfo handling */ + +MimeInfo *procmime_mimeinfo_new (void); +void procmime_mimeinfo_free_all (MimeInfo *mimeinfo); + +MimeInfo *procmime_mimeinfo_insert (MimeInfo *parent, + MimeInfo *mimeinfo); +#if 0 +void procmime_mimeinfo_replace (MimeInfo *old, + MimeInfo *new); +#endif + +MimeInfo *procmime_mimeinfo_next (MimeInfo *mimeinfo); + +MimeInfo *procmime_scan_message (MsgInfo *msginfo); +MimeInfo *procmime_scan_message_stream (FILE *fp); +void procmime_scan_multipart_message (MimeInfo *mimeinfo, + FILE *fp); + +/* scan headers */ + +void procmime_scan_encoding (MimeInfo *mimeinfo, + const gchar *encoding); +void procmime_scan_content_type (MimeInfo *mimeinfo, + const gchar *content_type); +void procmime_scan_content_type_str (const gchar *content_type, + gchar **mime_type, + gchar **charset, + gchar **name, + gchar **boundary); +void procmime_scan_content_type_partial (const gchar *content_type, + gint *total, + gchar **part_id, + gint *number); +void procmime_scan_content_disposition (MimeInfo *mimeinfo, + const gchar *content_disposition); +MimeInfo *procmime_scan_mime_header (FILE *fp); + +FILE *procmime_decode_content (FILE *outfp, + FILE *infp, + MimeInfo *mimeinfo); +gint procmime_get_part (const gchar *outfile, + const gchar *infile, + MimeInfo *mimeinfo); +gint procmime_get_part_fp (const gchar *outfile, + FILE *infp, + MimeInfo *mimeinfo); +FILE *procmime_get_part_fp_fp (FILE *outfp, + FILE *infp, + MimeInfo *mimeinfo); +gint procmime_get_all_parts (const gchar *dir, + const gchar *infile, + MimeInfo *mimeinfo); +FILE *procmime_get_text_content (MimeInfo *mimeinfo, + FILE *infp, + const gchar *encoding); +FILE *procmime_get_first_text_content (MsgInfo *msginfo, + const gchar *encoding); + +gboolean procmime_find_string_part (MimeInfo *mimeinfo, + const gchar *filename, + const gchar *str, + StrFindFunc find_func); +gboolean procmime_find_string (MsgInfo *msginfo, + const gchar *str, + StrFindFunc find_func); + +gchar *procmime_get_part_file_name (MimeInfo *mimeinfo); +gchar *procmime_get_tmp_file_name (MimeInfo *mimeinfo); +gchar *procmime_get_tmp_file_name_for_user + (MimeInfo *mimeinfo); + +ContentType procmime_scan_mime_type (const gchar *mime_type); +gchar *procmime_get_mime_type (const gchar *filename); + +gint procmime_execute_open_file (const gchar *file, + const gchar *mime_type); + +EncodingType procmime_get_encoding_for_charset (const gchar *charset); +EncodingType procmime_get_encoding_for_text_file(const gchar *file); +EncodingType procmime_get_encoding_for_str (const gchar *str); +const gchar *procmime_get_encoding_str (EncodingType encoding); + +#endif /* __PROCMIME_H__ */ diff --git a/libsylph/procmsg.c b/libsylph/procmsg.c new file mode 100644 index 0000000..28d16f3 --- /dev/null +++ b/libsylph/procmsg.c @@ -0,0 +1,2446 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2013 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "defs.h" + +#include +#include +#include +#include +#include + +#include "utils.h" +#include "procmsg.h" +#include "procheader.h" +#include "account.h" +#include "procmime.h" +#include "prefs_common.h" +#include "folder.h" +#include "codeconv.h" + +typedef struct _MsgFlagInfo { + guint msgnum; + MsgFlags flags; +} MsgFlagInfo; + +static GSList *procmsg_read_cache_queue (FolderItem *item, + gboolean scan_file); + +static void mark_sum_func (gpointer key, + gpointer value, + gpointer data); + +static GHashTable *procmsg_read_mark_file (FolderItem *item); +static void procmsg_write_mark_file (FolderItem *item, + GHashTable *mark_table); + +static GMappedFile *procmsg_open_cache_file_mmap(FolderItem *item, + DataOpenMode mode); + +static gint procmsg_cmp_by_mark (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_unread (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_mime (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_label (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_number (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_size (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_date (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_from (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_to (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_subject (gconstpointer a, + gconstpointer b); + + +GHashTable *procmsg_msg_hash_table_create(GSList *mlist) +{ + GHashTable *msg_table; + + if (mlist == NULL) return NULL; + + msg_table = g_hash_table_new(NULL, g_direct_equal); + procmsg_msg_hash_table_append(msg_table, mlist); + + return msg_table; +} + +void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist) +{ + GSList *cur; + MsgInfo *msginfo; + + if (msg_table == NULL || mlist == NULL) return; + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + g_hash_table_insert(msg_table, + GUINT_TO_POINTER(msginfo->msgnum), + msginfo); + } +} + +GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist) +{ + GHashTable *msg_table; + GSList *cur; + MsgInfo *msginfo; + + if (mlist == NULL) return NULL; + + msg_table = g_hash_table_new(NULL, g_direct_equal); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + g_hash_table_insert(msg_table, msginfo->to_folder, msginfo); + } + + return msg_table; +} + +gint procmsg_read_cache_data_str(FILE *fp, gchar **str) +{ + gchar buf[BUFFSIZE]; + guint32 len; + gchar *tmp = NULL; + + if (fread(&len, sizeof(len), 1, fp) != 1) + return -1; + + if (len > G_MAXINT) + return -1; + + while (len > 0) { + size_t size = MIN(len, BUFFSIZE - 1); + + if (fread(buf, size, 1, fp) != 1) { + if (tmp) + g_free(tmp); + *str = NULL; + return -1; + } + + buf[size] = '\0'; + if (tmp) { + *str = g_strconcat(tmp, buf, NULL); + g_free(tmp); + tmp = *str; + } else + tmp = *str = g_strdup(buf); + + len -= size; + } + + return 0; +} + +static gint procmsg_read_cache_data_str_mem(const gchar **p, const gchar *endp, gchar **str) +{ + guint32 len; + + if (endp - *p < sizeof(len)) + return -1; + + memcpy(&len, *p, sizeof(len)); + *p += sizeof(len); + if (len > G_MAXINT || len > endp - *p) + return -1; + + if (len > 0) { + *str = g_strndup(*p, len); + *p += len; + } + + return 0; +} + +#define READ_CACHE_DATA(data) \ +{ \ + if (procmsg_read_cache_data_str_mem(&p, endp, &data) < 0) { \ + g_warning("Cache data is corrupted\n"); \ + procmsg_msginfo_free(msginfo); \ + procmsg_msg_list_free(mlist); \ + g_mapped_file_free(mapfile); \ + return NULL; \ + } \ +} + +#define READ_CACHE_DATA_INT(n) \ +{ \ + if (endp - p < sizeof(guint32)) { \ + g_warning("Cache data is corrupted\n"); \ + procmsg_msginfo_free(msginfo); \ + procmsg_msg_list_free(mlist); \ + g_mapped_file_free(mapfile); \ + return NULL; \ + } else { \ + guint32 idata; \ + memcpy(&idata, p, sizeof(idata)); \ + n = idata; \ + p += sizeof(guint32); \ + } \ +} + +GSList *procmsg_read_cache(FolderItem *item, gboolean scan_file) +{ + GSList *mlist = NULL; + GSList *last = NULL; + GMappedFile *mapfile; + const gchar *filep; + gsize file_len; + const gchar *p, *endp; + MsgInfo *msginfo; + MsgFlags default_flags; + guint32 num; + guint refnum; + FolderType type; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->folder != NULL, NULL); + type = FOLDER_TYPE(item->folder); + + default_flags.perm_flags = MSG_NEW|MSG_UNREAD; + default_flags.tmp_flags = 0; + if (type == F_MH || type == F_IMAP) { + if (item->stype == F_QUEUE) { + MSG_SET_TMP_FLAGS(default_flags, MSG_QUEUED); + } else if (item->stype == F_DRAFT) { + MSG_SET_TMP_FLAGS(default_flags, MSG_DRAFT); + } + } + if (type == F_IMAP) { + MSG_SET_TMP_FLAGS(default_flags, MSG_IMAP); + } else if (type == F_NEWS) { + MSG_SET_TMP_FLAGS(default_flags, MSG_NEWS); + } + + if (type == F_MH) { + gchar *path; + + path = folder_item_get_path(item); + if (change_dir(path) < 0) { + g_free(path); + return NULL; + } + g_free(path); + } + + mapfile = procmsg_open_cache_file_mmap(item, DATA_READ); + if (!mapfile) { + item->cache_dirty = TRUE; + return NULL; + } + + debug_print("Reading summary cache...\n"); + + filep = g_mapped_file_get_contents(mapfile); + file_len = g_mapped_file_get_length(mapfile); + endp = filep + file_len; + p = filep + sizeof(guint32); /* version */ + + while (endp - p >= sizeof(num)) { + msginfo = g_new0(MsgInfo, 1); + + READ_CACHE_DATA_INT(msginfo->msgnum); + + READ_CACHE_DATA_INT(msginfo->size); + READ_CACHE_DATA_INT(msginfo->mtime); + READ_CACHE_DATA_INT(msginfo->date_t); + READ_CACHE_DATA_INT(msginfo->flags.tmp_flags); + + READ_CACHE_DATA(msginfo->fromname); + + READ_CACHE_DATA(msginfo->date); + READ_CACHE_DATA(msginfo->from); + READ_CACHE_DATA(msginfo->to); + READ_CACHE_DATA(msginfo->newsgroups); + READ_CACHE_DATA(msginfo->subject); + READ_CACHE_DATA(msginfo->msgid); + READ_CACHE_DATA(msginfo->inreplyto); + + READ_CACHE_DATA_INT(refnum); + for (; refnum != 0; refnum--) { + gchar *ref; + + READ_CACHE_DATA(ref); + msginfo->references = + g_slist_prepend(msginfo->references, ref); + } + if (msginfo->references) + msginfo->references = + g_slist_reverse(msginfo->references); + + MSG_SET_PERM_FLAGS(msginfo->flags, default_flags.perm_flags); + MSG_SET_TMP_FLAGS(msginfo->flags, default_flags.tmp_flags); + + /* if the message file doesn't exist or is changed, + don't add the data */ + if ((type == F_MH && scan_file && + folder_item_is_msg_changed(item, msginfo)) || + msginfo->msgnum == 0) { + procmsg_msginfo_free(msginfo); + item->cache_dirty = TRUE; + } else { + msginfo->folder = item; + + if (!mlist) + last = mlist = g_slist_append(NULL, msginfo); + else { + last = g_slist_append(last, msginfo); + last = last->next; + } + } + } + + g_mapped_file_free(mapfile); + + if (item->cache_queue) { + GSList *qlist; + qlist = procmsg_read_cache_queue(item, scan_file); + mlist = g_slist_concat(mlist, qlist); + } + + debug_print("done.\n"); + + return mlist; +} + +#undef READ_CACHE_DATA +#undef READ_CACHE_DATA_INT + +static GSList *procmsg_read_cache_queue(FolderItem *item, gboolean scan_file) +{ + FolderType type; + MsgInfo *msginfo; + MsgFlags default_flags; + GSList *cur; + GSList *qlist = NULL; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->folder != NULL, NULL); + + if (!item->cache_queue) + return NULL; + + debug_print("Reading cache queue...\n"); + + type = FOLDER_TYPE(item->folder); + default_flags.perm_flags = MSG_NEW|MSG_UNREAD; + default_flags.tmp_flags = 0; + + for (cur = item->cache_queue; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + debug_print("read cache queue: %s/%d\n", + item->path, msginfo->msgnum); + + MSG_SET_PERM_FLAGS(msginfo->flags, default_flags.perm_flags); + MSG_SET_TMP_FLAGS(msginfo->flags, default_flags.tmp_flags); + + if ((type == F_MH && scan_file && + folder_item_is_msg_changed(item, msginfo))) { + procmsg_msginfo_free(msginfo); + item->cache_dirty = TRUE; + } else { + msginfo->folder = item; + qlist = g_slist_prepend(qlist, msginfo); + } + } + + g_slist_free(item->cache_queue); + item->cache_queue = NULL; + item->cache_dirty = TRUE; + + return qlist; +} + +static void mark_unset_new_func(gpointer key, gpointer value, gpointer data) +{ + MSG_UNSET_PERM_FLAGS(*((MsgFlags *)value), MSG_NEW); +} + +void procmsg_set_flags(GSList *mlist, FolderItem *item) +{ + GSList *cur; + gint new = 0, unread = 0, total = 0; + gint lastnum = 0; + gint unflagged = 0; + gboolean mark_queue_exist; + MsgInfo *msginfo; + GHashTable *mark_table; + MsgFlags *flags; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + debug_print("Marking the messages...\n"); + + mark_queue_exist = (item->mark_queue != NULL); + mark_table = procmsg_read_mark_file(item); + if (!mark_table) { + item->new = item->unread = item->total = g_slist_length(mlist); + item->updated = TRUE; + item->mark_dirty = TRUE; + return; + } + + /* unset new flags if new (unflagged) messages exist */ + if (!mark_queue_exist) { + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + flags = g_hash_table_lookup + (mark_table, GUINT_TO_POINTER(msginfo->msgnum)); + if (!flags) { + g_hash_table_foreach(mark_table, + mark_unset_new_func, NULL); + item->mark_dirty = TRUE; + break; + } + } + } + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + if (lastnum < msginfo->msgnum) + lastnum = msginfo->msgnum; + + flags = g_hash_table_lookup + (mark_table, GUINT_TO_POINTER(msginfo->msgnum)); + + if (flags != NULL) { + /* add the permanent flags only */ + msginfo->flags.perm_flags = flags->perm_flags; + if (MSG_IS_NEW(*flags)) + ++new; + if (MSG_IS_UNREAD(*flags)) + ++unread; + if (FOLDER_TYPE(item->folder) == F_IMAP) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_IMAP); + } else if (FOLDER_TYPE(item->folder) == F_NEWS) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_NEWS); + } + } else { + ++unflagged; + ++new; + ++unread; + } + + ++total; + } + + item->new = new; + item->unread = unread; + item->total = total; + item->unmarked_num = unflagged; + item->last_num = lastnum; + item->updated = TRUE; + + if (unflagged > 0) + item->mark_dirty = TRUE; + + debug_print("new: %d unread: %d unflagged: %d total: %d\n", + new, unread, unflagged, total); + + hash_free_value_mem(mark_table); + g_hash_table_destroy(mark_table); +} + +static void mark_all_read_func(gpointer key, gpointer value, gpointer data) +{ + MSG_UNSET_PERM_FLAGS(*((MsgFlags *)value), MSG_NEW|MSG_UNREAD); +} + +void procmsg_mark_all_read(FolderItem *item) +{ + GHashTable *mark_table; + + debug_print("Marking all messages as read\n"); + + mark_table = procmsg_read_mark_file(item); + if (mark_table) { + g_hash_table_foreach(mark_table, mark_all_read_func, NULL); + procmsg_write_mark_file(item, mark_table); + hash_free_value_mem(mark_table); + g_hash_table_destroy(mark_table); + } + + if (item->mark_queue) { + GSList *cur; + MsgFlagInfo *flaginfo; + + for (cur = item->mark_queue; cur != NULL; cur = cur->next) { + flaginfo = (MsgFlagInfo *)cur->data; + MSG_UNSET_PERM_FLAGS + (flaginfo->flags, MSG_NEW|MSG_UNREAD); + } + item->mark_dirty = TRUE; + } + + item->new = item->unread = 0; +} + +static FolderSortType cmp_func_sort_type; + +GSList *procmsg_sort_msg_list(GSList *mlist, FolderSortKey sort_key, + FolderSortType sort_type) +{ + GCompareFunc cmp_func; + + switch (sort_key) { + case SORT_BY_MARK: + cmp_func = procmsg_cmp_by_mark; break; + case SORT_BY_UNREAD: + cmp_func = procmsg_cmp_by_unread; break; + case SORT_BY_MIME: + cmp_func = procmsg_cmp_by_mime; break; + case SORT_BY_LABEL: + cmp_func = procmsg_cmp_by_label; break; + case SORT_BY_NUMBER: + cmp_func = procmsg_cmp_by_number; break; + case SORT_BY_SIZE: + cmp_func = procmsg_cmp_by_size; break; + case SORT_BY_DATE: + cmp_func = procmsg_cmp_by_date; break; + case SORT_BY_FROM: + cmp_func = procmsg_cmp_by_from; break; + case SORT_BY_SUBJECT: + cmp_func = procmsg_cmp_by_subject; break; + case SORT_BY_TO: + cmp_func = procmsg_cmp_by_to; break; + default: + return mlist; + } + + cmp_func_sort_type = sort_type; + + mlist = g_slist_sort(mlist, cmp_func); + + return mlist; +} + +gint procmsg_get_last_num_in_msg_list(GSList *mlist) +{ + GSList *cur; + MsgInfo *msginfo; + gint last = 0; + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + if (msginfo && msginfo->msgnum > last) + last = msginfo->msgnum; + } + + return last; +} + +void procmsg_msg_list_free(GSList *mlist) +{ + GSList *cur; + MsgInfo *msginfo; + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + procmsg_msginfo_free(msginfo); + } + g_slist_free(mlist); +} + +void procmsg_write_cache(MsgInfo *msginfo, FILE *fp) +{ + MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK; + GSList *cur; + + WRITE_CACHE_DATA_INT(msginfo->msgnum, fp); + WRITE_CACHE_DATA_INT(msginfo->size, fp); + WRITE_CACHE_DATA_INT(msginfo->mtime, fp); + WRITE_CACHE_DATA_INT(msginfo->date_t, fp); + WRITE_CACHE_DATA_INT(flags, fp); + + WRITE_CACHE_DATA(msginfo->fromname, fp); + + WRITE_CACHE_DATA(msginfo->date, fp); + WRITE_CACHE_DATA(msginfo->from, fp); + WRITE_CACHE_DATA(msginfo->to, fp); + WRITE_CACHE_DATA(msginfo->newsgroups, fp); + WRITE_CACHE_DATA(msginfo->subject, fp); + WRITE_CACHE_DATA(msginfo->msgid, fp); + WRITE_CACHE_DATA(msginfo->inreplyto, fp); + + WRITE_CACHE_DATA_INT(g_slist_length(msginfo->references), fp); + for (cur = msginfo->references; cur != NULL; cur = cur->next) { + WRITE_CACHE_DATA((gchar *)cur->data, fp); + } +} + +void procmsg_write_flags(MsgInfo *msginfo, FILE *fp) +{ + MsgPermFlags flags = msginfo->flags.perm_flags; + + WRITE_CACHE_DATA_INT(msginfo->msgnum, fp); + WRITE_CACHE_DATA_INT(flags, fp); +} + +void procmsg_write_cache_list(FolderItem *item, GSList *mlist) +{ + FILE *fp; + GSList *cur; + + g_return_if_fail(item != NULL); + + debug_print("Writing summary cache (%s)\n", item->path); + + fp = procmsg_open_cache_file(item, DATA_WRITE); + if (fp == NULL) + return; + + for (cur = mlist; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + procmsg_write_cache(msginfo, fp); + } + + if (item->cache_queue) + procmsg_flush_cache_queue(item, fp); + + fclose(fp); + item->cache_dirty = FALSE; +} + +void procmsg_write_flags_list(FolderItem *item, GSList *mlist) +{ + FILE *fp; + GSList *cur; + + g_return_if_fail(item != NULL); + + debug_print("Writing summary flags (%s)\n", item->path); + + fp = procmsg_open_mark_file(item, DATA_WRITE); + if (fp == NULL) + return; + + for (cur = mlist; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + procmsg_write_flags(msginfo, fp); + } + + if (item->mark_queue) + procmsg_flush_mark_queue(item, fp); + + fclose(fp); + item->mark_dirty = FALSE; +} + +static gint cmp_by_item(gconstpointer a, gconstpointer b) +{ + const MsgInfo *msginfo1 = a; + const MsgInfo *msginfo2 = b; + + if (msginfo1->folder == msginfo2->folder) + return msginfo1->msgnum - msginfo2->msgnum; + + return msginfo1->folder - msginfo2->folder; +} + +void procmsg_write_flags_for_multiple_folders(GSList *mlist) +{ + GSList *tmp_list, *cur; + FolderItem *prev_item = NULL; + FILE *fp = NULL; + + if (!mlist) + return; + + tmp_list = g_slist_copy(mlist); + tmp_list = g_slist_sort(tmp_list, cmp_by_item); + + for (cur = tmp_list; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + FolderItem *item = msginfo->folder; + + if (prev_item != item) { + if (fp) + fclose(fp); + fp = procmsg_open_mark_file(item, DATA_APPEND); + if (!fp) { + g_warning("can't open mark file\n"); + break; + } + item->updated = TRUE; + } + procmsg_write_flags(msginfo, fp); + prev_item = item; + } + + if (fp) + fclose(fp); + g_slist_free(tmp_list); +} + +void procmsg_flush_mark_queue(FolderItem *item, FILE *fp) +{ + MsgFlagInfo *flaginfo; + MsgInfo msginfo = {0}; + gboolean append = FALSE; + GSList *qlist, *cur; + + g_return_if_fail(item != NULL); + + if (!item->mark_queue) + return; + + debug_print("flushing mark_queue: %s ...\n", item->path); + + if (!fp) { + append = TRUE; + fp = procmsg_open_mark_file(item, DATA_APPEND); + g_return_if_fail(fp != NULL); + } + + qlist = g_slist_reverse(item->mark_queue); + item->mark_queue = NULL; + + for (cur = qlist; cur != NULL; cur = cur->next) { + flaginfo = (MsgFlagInfo *)cur->data; + + msginfo.msgnum = flaginfo->msgnum; + msginfo.flags = flaginfo->flags; + procmsg_write_flags(&msginfo, fp); + g_free(flaginfo); + } + + g_slist_free(qlist); + + if (append) + fclose(fp); +} + +void procmsg_add_mark_queue(FolderItem *item, gint num, MsgFlags flags) +{ + MsgFlagInfo *flaginfo; + + flaginfo = g_new(MsgFlagInfo, 1); + flaginfo->msgnum = num; + flaginfo->flags = flags; + item->mark_queue = g_slist_prepend(item->mark_queue, flaginfo); +} + +void procmsg_flaginfo_list_free(GSList *flaglist) +{ + GSList *cur; + MsgFlagInfo *flaginfo; + + for (cur = flaglist; cur != NULL; cur = cur->next) { + flaginfo = (MsgFlagInfo *)cur->data; + g_free(flaginfo); + } + g_slist_free(flaglist); +} + +void procmsg_flush_cache_queue(FolderItem *item, FILE *fp) +{ + MsgInfo *msginfo; + gboolean append = FALSE; + GSList *qlist, *cur; + + g_return_if_fail(item != NULL); + + if (!item->cache_queue) + return; + + debug_print("flushing cache_queue: %s ...\n", item->path); + + if (!fp) { + append = TRUE; + fp = procmsg_open_cache_file(item, DATA_APPEND); + g_return_if_fail(fp != NULL); + } + + qlist = g_slist_reverse(item->cache_queue); + item->cache_queue = NULL; + + for (cur = qlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + debug_print("flush cache queue: %s/%d\n", + item->path, msginfo->msgnum); + procmsg_write_cache(msginfo, fp); + procmsg_msginfo_free(msginfo); + } + + g_slist_free(qlist); + + if (append) + fclose(fp); +} + +void procmsg_add_cache_queue(FolderItem *item, gint num, MsgInfo *msginfo) +{ + MsgInfo *queue_msginfo; + + g_return_if_fail(msginfo != NULL); + + queue_msginfo = procmsg_msginfo_copy(msginfo); + queue_msginfo->msgnum = num; + queue_msginfo->folder = item; + if (queue_msginfo->file_path) { + g_free(queue_msginfo->file_path); + queue_msginfo->file_path = NULL; + } + + debug_print("procmsg_add_cache_queue: add msg cache: %s/%d\n", + item->path, num); + item->cache_queue = g_slist_prepend(item->cache_queue, queue_msginfo); +} + +gboolean procmsg_flush_folder(FolderItem *item) +{ + gboolean flushed = FALSE; + gint n_new, n_unread, n_total, n_min, n_max; + + g_return_val_if_fail(item != NULL, FALSE); + g_return_val_if_fail(item->folder != NULL, FALSE); + + if (FOLDER_TYPE(item->folder) != F_MH || item->last_num < 0) { + folder_item_scan(item); + return TRUE; + } + + if (item->mark_queue && !item->opened) + flushed = TRUE; + procmsg_get_mark_sum(item, &n_new, &n_unread, &n_total, &n_min, &n_max, + 0); + item->unmarked_num = 0; + item->new = n_new; + item->unread = n_unread; + item->total = n_total; + + if (item->cache_queue && !item->opened) { + procmsg_flush_cache_queue(item, NULL); + flushed = TRUE; + } + + if (flushed) + debug_print("procmsg_flush_folder: flushed %s\n", item->path); + + return flushed; +} + +static void procmsg_flush_folder_foreach_func(gpointer key, gpointer val, + gpointer data) +{ + procmsg_flush_folder(FOLDER_ITEM(key)); +} + +void procmsg_flush_folder_foreach(GHashTable *folder_table) +{ + g_hash_table_foreach(folder_table, procmsg_flush_folder_foreach_func, + NULL); +} + +void procmsg_add_flags(FolderItem *item, gint num, MsgFlags flags) +{ + FILE *fp; + MsgInfo msginfo; + + g_return_if_fail(item != NULL); + + if (item->opened) { + procmsg_add_mark_queue(item, num, flags); + return; + } + + if ((fp = procmsg_open_mark_file(item, DATA_APPEND)) == NULL) { + g_warning(_("can't open mark file\n")); + return; + } + + msginfo.msgnum = num; + msginfo.flags = flags; + + procmsg_write_flags(&msginfo, fp); + fclose(fp); +} + +struct MarkSum { + gint *new; + gint *unread; + gint *total; + gint *min; + gint *max; + gint first; +}; + +static void mark_sum_func(gpointer key, gpointer value, gpointer data) +{ + MsgFlags *flags = value; + gint num = GPOINTER_TO_INT(key); + struct MarkSum *marksum = data; + + if (marksum->first <= num) { + if (MSG_IS_NEW(*flags)) (*marksum->new)++; + if (MSG_IS_UNREAD(*flags)) (*marksum->unread)++; + if (num > *marksum->max) *marksum->max = num; + if (num < *marksum->min || *marksum->min == 0) *marksum->min = num; + (*marksum->total)++; + } + + g_free(flags); +} + +void procmsg_get_mark_sum(FolderItem *item, + gint *new, gint *unread, gint *total, + gint *min, gint *max, + gint first) +{ + GHashTable *mark_table; + struct MarkSum marksum; + + *new = *unread = *total = *min = *max = 0; + marksum.new = new; + marksum.unread = unread; + marksum.total = total; + marksum.min = min; + marksum.max = max; + marksum.first = first; + + mark_table = procmsg_read_mark_file(item); + + if (mark_table) { + g_hash_table_foreach(mark_table, mark_sum_func, &marksum); + g_hash_table_destroy(mark_table); + } +} + +static GHashTable *procmsg_read_mark_file(FolderItem *item) +{ + FILE *fp; + GHashTable *mark_table = NULL; + guint32 idata; + guint num; + MsgFlags *flags; + MsgPermFlags perm_flags; + GSList *cur; + + if ((fp = procmsg_open_mark_file(item, DATA_READ)) == NULL) + return NULL; + + mark_table = g_hash_table_new(NULL, g_direct_equal); + + while (fread(&idata, sizeof(idata), 1, fp) == 1) { + num = idata; + if (fread(&idata, sizeof(idata), 1, fp) != 1) break; + perm_flags = idata; + + flags = g_hash_table_lookup(mark_table, GUINT_TO_POINTER(num)); + if (flags != NULL) + g_free(flags); + + flags = g_new0(MsgFlags, 1); + flags->perm_flags = perm_flags; + + g_hash_table_insert(mark_table, GUINT_TO_POINTER(num), flags); + } + + fclose(fp); + + if (item->mark_queue) { + g_hash_table_foreach(mark_table, mark_unset_new_func, NULL); + item->mark_dirty = TRUE; + } + + for (cur = item->mark_queue; cur != NULL; cur = cur->next) { + MsgFlagInfo *flaginfo = (MsgFlagInfo *)cur->data; + + flags = g_hash_table_lookup(mark_table, + GUINT_TO_POINTER(flaginfo->msgnum)); + if (flags != NULL) + g_free(flags); + + flags = g_new0(MsgFlags, 1); + flags->perm_flags = flaginfo->flags.perm_flags; + + g_hash_table_insert(mark_table, + GUINT_TO_POINTER(flaginfo->msgnum), flags); + + } + + if (item->mark_queue && !item->opened) { + procmsg_write_mark_file(item, mark_table); + procmsg_flaginfo_list_free(item->mark_queue); + item->mark_queue = NULL; + item->mark_dirty = FALSE; + } + + return mark_table; +} + +static void write_mark_func(gpointer key, gpointer value, gpointer data) +{ + MsgInfo msginfo; + + msginfo.msgnum = GPOINTER_TO_UINT(key); + msginfo.flags.perm_flags = ((MsgFlags *)value)->perm_flags; + procmsg_write_flags(&msginfo, (FILE *)data); +} + +static void procmsg_write_mark_file(FolderItem *item, GHashTable *mark_table) +{ + FILE *fp; + + if ((fp = procmsg_open_mark_file(item, DATA_WRITE)) == NULL) { + g_warning("procmsg_write_mark_file: cannot open mark file."); + return; + } + g_hash_table_foreach(mark_table, write_mark_func, fp); + fclose(fp); +} + +FILE *procmsg_open_data_file(const gchar *file, guint version, + DataOpenMode mode, gchar *buf, size_t buf_size) +{ + FILE *fp; + guint32 data_ver = 0; + + g_return_val_if_fail(file != NULL, NULL); + + if (mode == DATA_WRITE) { + if ((fp = g_fopen(file, "wb")) == NULL) { + if (errno == EACCES) { + change_file_mode_rw(NULL, file); + if ((fp = g_fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "procmsg_open_data_file: fopen"); + return NULL; + } + } else { + FILE_OP_ERROR(file, "procmsg_open_data_file: fopen"); + return NULL; + } + } + if (change_file_mode_rw(fp, file) < 0) + FILE_OP_ERROR(file, "chmod"); + + WRITE_CACHE_DATA_INT(version, fp); + return fp; + } + + /* check version */ + if ((fp = g_fopen(file, "rb")) == NULL) { + if (errno == EACCES) { + change_file_mode_rw(NULL, file); + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "procmsg_open_data_file: fopen"); + } + } else { + debug_print("Mark/Cache file '%s' not found\n", file); + } + } + + if (fp) { + if (buf && buf_size > 0) + setvbuf(fp, buf, _IOFBF, buf_size); + if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1) { + g_warning("%s: cannot read mark/cache file (truncated?)\n", file); + fclose(fp); + fp = NULL; + } else if (version != data_ver) { + g_message("%s: Mark/Cache version is different (%u != %u). Discarding it.\n", + file, data_ver, version); + fclose(fp); + fp = NULL; + } + } + + if (mode == DATA_READ) + return fp; + + if (fp) { + /* reopen with append mode */ + fclose(fp); + if ((fp = g_fopen(file, "ab")) == NULL) { + if (errno == EACCES) { + change_file_mode_rw(NULL, file); + if ((fp = g_fopen(file, "ab")) == NULL) { + FILE_OP_ERROR(file, "procmsg_open_data_file: fopen"); + } + } else { + FILE_OP_ERROR(file, "procmsg_open_data_file: fopen"); + } + } + } else { + /* open with overwrite mode if mark file doesn't exist or + version is different */ + fp = procmsg_open_data_file(file, version, DATA_WRITE, buf, + buf_size); + } + + return fp; +} + +static GMappedFile *procmsg_open_cache_file_mmap(FolderItem *item, + DataOpenMode mode) +{ + gchar *cachefile; + GMappedFile *map = NULL; + GError *error = NULL; + gsize size; + guint32 data_ver = 0; + gchar *p; + + if (mode != DATA_READ) + return NULL; + + cachefile = folder_item_get_cache_file(item); + if (cachefile) { + map = g_mapped_file_new(cachefile, FALSE, &error); + if (!map) { + if (error && error->code == G_FILE_ERROR_NOENT) + debug_print("%s: mark/cache file not found\n", cachefile); + else if (error) + g_warning("%s: cannot open mark/cache file: %s", cachefile, error->message); + else + g_warning("%s: cannot open mark/cache file", cachefile); + if (error) + g_error_free(error); + g_free(cachefile); + return NULL; + } + size = g_mapped_file_get_length(map); + if (size < sizeof(data_ver)) { + g_warning("%s: cannot read mark/cache file (truncated?)", cachefile); + g_mapped_file_free(map); + g_free(cachefile); + return NULL; + } + p = g_mapped_file_get_contents(map); + data_ver = *(guint32 *)p; + if (CACHE_VERSION != data_ver) { + g_message("%s: Mark/Cache version is different (%u != %u). Discarding it.\n", + cachefile, data_ver, CACHE_VERSION); + g_mapped_file_free(map); + g_free(cachefile); + return NULL; + } + g_free(cachefile); + } + + return map; +} + +FILE *procmsg_open_cache_file(FolderItem *item, DataOpenMode mode) +{ + gchar *cachefile; + FILE *fp; + + cachefile = folder_item_get_cache_file(item); + fp = procmsg_open_data_file(cachefile, CACHE_VERSION, mode, NULL, 0); + g_free(cachefile); + + return fp; +} + +FILE *procmsg_open_mark_file(FolderItem *item, DataOpenMode mode) +{ + gchar *markfile; + FILE *fp; + + markfile = folder_item_get_mark_file(item); + fp = procmsg_open_data_file(markfile, MARK_VERSION, mode, NULL, 0); + g_free(markfile); + + return fp; +} + +void procmsg_clear_cache(FolderItem *item) +{ + FILE *fp; + + fp = procmsg_open_cache_file(item, DATA_WRITE); + if (fp) + fclose(fp); +} + +void procmsg_clear_mark(FolderItem *item) +{ + FILE *fp; + + fp = procmsg_open_mark_file(item, DATA_WRITE); + if (fp) + fclose(fp); +} + +/* return the reversed thread tree */ +GNode *procmsg_get_thread_tree(GSList *mlist) +{ + GNode *root, *parent, *node, *next; + GHashTable *table; + MsgInfo *msginfo; + const gchar *msgid; + GSList *reflist; + + root = g_node_new(NULL); + table = g_hash_table_new(g_str_hash, g_str_equal); + + for (; mlist != NULL; mlist = mlist->next) { + msginfo = (MsgInfo *)mlist->data; + parent = root; + + /* only look for the real parent first */ + if (msginfo->inreplyto) { + parent = g_hash_table_lookup(table, msginfo->inreplyto); + if (parent == NULL) + parent = root; + } + + node = g_node_insert_data_before + (parent, parent == root ? parent->children : NULL, + msginfo); + if ((msgid = msginfo->msgid) && + g_hash_table_lookup(table, msgid) == NULL) + g_hash_table_insert(table, (gchar *)msgid, node); + } + + /* complete the unfinished threads */ + for (node = root->children; node != NULL; ) { + next = node->next; + msginfo = (MsgInfo *)node->data; + parent = NULL; + + if (msginfo->inreplyto) + parent = g_hash_table_lookup(table, msginfo->inreplyto); + + /* try looking for the indirect parent */ + if (!parent && msginfo->references) { + for (reflist = msginfo->references; + reflist != NULL; reflist = reflist->next) + if ((parent = g_hash_table_lookup + (table, reflist->data)) != NULL) + break; + } + + /* node should not be the parent, and node should not + be an ancestor of parent (circular reference) */ + if (parent && parent != node && + !g_node_is_ancestor(node, parent)) { + g_node_unlink(node); + g_node_insert_before + (parent, parent->children, node); + } + node = next; + } + + g_hash_table_destroy(table); + + return root; +} + +static gboolean procmsg_thread_date_func(GNode *node, gpointer data) +{ + guint *tdate = (guint *)data; + MsgInfo *msginfo = (MsgInfo *)node->data; + + if (*tdate < msginfo->date_t) + *tdate = msginfo->date_t; + + return FALSE; +} + +guint procmsg_get_thread_date(GNode *node) +{ + guint tdate = 0; + + g_return_val_if_fail(node != NULL && node->parent != NULL && + node->parent->parent == NULL, 0); + + g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + procmsg_thread_date_func, &tdate); + + return tdate; +} + +gint procmsg_move_messages(GSList *mlist) +{ + GSList *cur, *movelist = NULL; + MsgInfo *msginfo; + FolderItem *dest = NULL; + GHashTable *hash; + gint val = 0; + + if (!mlist) return 0; + + hash = procmsg_to_folder_hash_table_create(mlist); + folder_item_scan_foreach(hash); + g_hash_table_destroy(hash); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + if (!dest) { + dest = msginfo->to_folder; + movelist = g_slist_append(movelist, msginfo); + } else if (dest == msginfo->to_folder) { + movelist = g_slist_append(movelist, msginfo); + } else { + val = folder_item_move_msgs(dest, movelist); + g_slist_free(movelist); + movelist = NULL; + if (val == -1) + return val; + dest = msginfo->to_folder; + movelist = g_slist_append(movelist, msginfo); + } + } + + if (movelist) { + val = folder_item_move_msgs(dest, movelist); + g_slist_free(movelist); + } + + return val == -1 ? -1 : 0; +} + +gint procmsg_copy_messages(GSList *mlist) +{ + GSList *cur, *copylist = NULL; + MsgInfo *msginfo; + FolderItem *dest = NULL; + GHashTable *hash; + gint val = 0; + + if (!mlist) return 0; + + hash = procmsg_to_folder_hash_table_create(mlist); + folder_item_scan_foreach(hash); + g_hash_table_destroy(hash); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + if (!dest) { + dest = msginfo->to_folder; + copylist = g_slist_append(copylist, msginfo); + } else if (dest == msginfo->to_folder) { + copylist = g_slist_append(copylist, msginfo); + } else { + val = folder_item_copy_msgs(dest, copylist); + g_slist_free(copylist); + copylist = NULL; + if (val == -1) + return val; + dest = msginfo->to_folder; + copylist = g_slist_append(copylist, msginfo); + } + } + + if (copylist) { + val = folder_item_copy_msgs(dest, copylist); + g_slist_free(copylist); + } + + return val == -1 ? -1 : 0; +} + +gint procmsg_add_messages_from_queue(FolderItem *dest, GSList *mlist, + gboolean is_move) +{ + MsgInfo *msginfo; + GSList *cur; + gchar *file; + FILE *fp; + gchar buf[BUFFSIZE]; + gchar *dest_file; + gboolean is_error = FALSE; + FolderItem *src; + MsgFlags flags; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(mlist != NULL, -1); + + msginfo = (MsgInfo *)mlist->data; + if (!msginfo || !msginfo->folder || msginfo->folder->stype != F_QUEUE || + !MSG_IS_QUEUED(msginfo->flags) || dest->stype == F_QUEUE) + return -1; + + debug_print("procmsg_add_messages_from_queue: adding messages from queue folder\n"); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + flags = msginfo->flags; + if (!MSG_IS_QUEUED(flags)) + return -1; + MSG_UNSET_TMP_FLAGS(flags, MSG_QUEUED); + src = msginfo->folder; + file = procmsg_get_message_file(msginfo); + if (!file) + return -1; + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "folder_item_move_msgs: fopen"); + g_free(file); + return -1; + } + while (fgets(buf, sizeof(buf), fp) != NULL) { + if (buf[0] == '\r' || buf[0] == '\n') + break; + } + if (ferror(fp)) { + fclose(fp); + g_free(file); + return -1; + } + + dest_file = get_tmp_file(); + debug_print("copy queued msg: %s -> %s\n", file, dest_file); + + if (copy_file_part(fp, ftell(fp), G_MAXINT, dest_file) < 0) { + fclose(fp); + is_error = TRUE; + } else { + fclose(fp); + if (folder_item_add_msg(dest, dest_file, &flags, TRUE) < 0) { + g_unlink(dest_file); + is_error = TRUE; + } else if (is_move && + folder_item_remove_msg(src, msginfo) < 0) + is_error = TRUE; + } + + g_free(dest_file); + g_free(file); + if (is_error) + return -1; + } + + return 0; +} + +gchar *procmsg_get_message_file_path(MsgInfo *msginfo) +{ + gchar *path, *file; + + g_return_val_if_fail(msginfo != NULL, NULL); + + if (msginfo->encinfo && msginfo->encinfo->plaintext_file) + file = g_strdup(msginfo->encinfo->plaintext_file); + else if (msginfo->file_path) + return g_strdup(msginfo->file_path); + else { + gchar nstr[16]; + path = folder_item_get_path(msginfo->folder); + file = g_strconcat(path, G_DIR_SEPARATOR_S, + utos_buf(nstr, msginfo->msgnum), NULL); + g_free(path); + } + + return file; +} + +gchar *procmsg_get_message_file(MsgInfo *msginfo) +{ + gchar *filename = NULL; + + g_return_val_if_fail(msginfo != NULL, NULL); + + if (msginfo->file_path) + return g_strdup(msginfo->file_path); + + filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum); + if (!filename) + debug_print(_("can't fetch message %d\n"), msginfo->msgnum); + + return filename; +} + +GSList *procmsg_get_message_file_list(GSList *mlist) +{ + GSList *file_list = NULL; + MsgInfo *msginfo; + MsgFileInfo *fileinfo; + gchar *file; + + while (mlist != NULL) { + msginfo = (MsgInfo *)mlist->data; + file = procmsg_get_message_file(msginfo); + if (!file) { + procmsg_message_file_list_free(file_list); + return NULL; + } + fileinfo = g_new(MsgFileInfo, 1); + fileinfo->file = file; + fileinfo->flags = g_new(MsgFlags, 1); + *fileinfo->flags = msginfo->flags; + file_list = g_slist_prepend(file_list, fileinfo); + mlist = mlist->next; + } + + file_list = g_slist_reverse(file_list); + + return file_list; +} + +void procmsg_message_file_list_free(GSList *file_list) +{ + GSList *cur; + MsgFileInfo *fileinfo; + + for (cur = file_list; cur != NULL; cur = cur->next) { + fileinfo = (MsgFileInfo *)cur->data; + g_free(fileinfo->file); + g_free(fileinfo->flags); + g_free(fileinfo); + } + + g_slist_free(file_list); +} + +FILE *procmsg_open_message(MsgInfo *msginfo) +{ + FILE *fp; + gchar *file; + + g_return_val_if_fail(msginfo != NULL, NULL); + + file = procmsg_get_message_file_path(msginfo); + g_return_val_if_fail(file != NULL, NULL); + + if (!is_file_exist(file)) { + g_free(file); + file = procmsg_get_message_file(msginfo); + if (!file) + return NULL; + } + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "procmsg_open_message: fopen"); + g_free(file); + return NULL; + } + + g_free(file); + + if (MSG_IS_QUEUED(msginfo->flags)) { + gchar buf[BUFFSIZE]; + + while (fgets(buf, sizeof(buf), fp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + } + + return fp; +} + +static DecryptMessageFunc decrypt_message_func = NULL; +static gboolean auto_decrypt = TRUE; + +void procmsg_set_decrypt_message_func(DecryptMessageFunc func) +{ + decrypt_message_func = func; +} + +void procmsg_set_auto_decrypt_message(gboolean enabled) +{ + auto_decrypt = enabled; +} + +FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo) +{ + FILE *fp; + + if (decrypt_message_func && auto_decrypt) + return decrypt_message_func(msginfo, mimeinfo); + + *mimeinfo = NULL; + if ((fp = procmsg_open_message(msginfo)) == NULL) + return NULL; + *mimeinfo = procmime_scan_mime_header(fp); + + return fp; +} + +gboolean procmsg_msg_exist(MsgInfo *msginfo) +{ + gchar *path; + gboolean ret; + + if (!msginfo) return FALSE; + + path = folder_item_get_path(msginfo->folder); + change_dir(path); + ret = !folder_item_is_msg_changed(msginfo->folder, msginfo); + g_free(path); + + return ret; +} + +gboolean procmsg_trash_messages_exist(void) +{ + FolderItem *trash; + GList *cur; + + for (cur = folder_get_list(); cur != NULL; cur = cur->next) { + trash = FOLDER(cur->data)->trash; + if (trash && trash->total > 0) + return TRUE; + } + + return FALSE; +} + +void procmsg_empty_trash(FolderItem *trash) +{ + if (!trash) + return; + + g_return_if_fail(trash->stype == F_TRASH || trash->stype == F_JUNK); + + if (trash->total > 0) { + debug_print("Emptying messages in %s ...\n", trash->path); + + folder_item_remove_all_msg(trash); + procmsg_clear_cache(trash); + procmsg_clear_mark(trash); + trash->cache_dirty = FALSE; + trash->mark_dirty = FALSE; + } +} + +void procmsg_empty_all_trash(void) +{ + FolderItem *trash; + GList *cur; + + for (cur = folder_get_list(); cur != NULL; cur = cur->next) { + trash = FOLDER(cur->data)->trash; + procmsg_empty_trash(trash); + } +} + +static gboolean remove_all_cached_messages_func(GNode *node, gpointer data) +{ + FolderItem *item; + gchar *dir; + + g_return_val_if_fail(node->data != NULL, FALSE); + + item = FOLDER_ITEM(node->data); + if (!item->path || item->stype == F_VIRTUAL) + return FALSE; + + dir = folder_item_get_path(item); + if (is_dir_exist(dir)) { + debug_print("removing all cached messages in '%s' ...\n", + item->path); + remove_all_numbered_files(dir); + } + g_free(dir); + + return FALSE; +} + +void procmsg_remove_all_cached_messages(Folder *folder) +{ + g_return_if_fail(folder != NULL); + g_return_if_fail(FOLDER_IS_REMOTE(folder)); + + debug_print("Removing all caches in the mailbox '%s' ...\n", + folder->name); + + g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + remove_all_cached_messages_func, NULL); +} + +gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file) +{ + gint num; + MsgFlags flag = {0, 0}; + + debug_print("saving sent message...\n"); + + if (!outbox) + outbox = folder_get_default_outbox(); + g_return_val_if_fail(outbox != NULL, -1); + + folder_item_scan(outbox); + if ((num = folder_item_add_msg(outbox, file, &flag, FALSE)) < 0) { + g_warning("can't save message\n"); + return -1; + } + procmsg_flush_folder(outbox); + + return 0; +} + +static guint print_id = 0; + +static gint print_command_exec(const gchar *file, const gchar *cmdline) +{ + static const gchar *def_cmd = "lpr %s"; + gchar buf[1024]; + +#ifdef G_OS_WIN32 + if (canonicalize_file_replace(file) < 0) + return -1; +#endif + + if (cmdline && str_find_format_times(cmdline, 's') == 1) + g_snprintf(buf, sizeof(buf) - 1, cmdline, file); + else { + if (cmdline) { + g_warning(_("Print command line is invalid: `%s'\n"), + cmdline); + return -1; + } + +#ifdef G_OS_WIN32 + execute_print_file(file); + return 0; +#else + g_snprintf(buf, sizeof(buf) - 1, def_cmd, file); +#endif + } + + g_strchomp(buf); + if (buf[strlen(buf) - 1] != '&') + strcat(buf, "&"); + + if (system(buf) != 0) + return -1; + + return 0; +} + +static void procmsg_write_headers(MsgInfo *msginfo, MimeInfo *partinfo, + FILE *fp, FILE *dest_fp, + const gchar *encoding, gboolean all_headers) +{ + GPtrArray *headers; + gint i; + + if (all_headers) + headers = procheader_get_header_array_asis(fp, NULL); + else + headers = procheader_get_header_array_for_display(fp, NULL); + + for (i = 0; i < headers->len; i++) { + Header *hdr; + gchar *file_str; + const gchar *body; + + hdr = g_ptr_array_index(headers, i); + + if (partinfo) { + if (!g_ascii_strcasecmp(hdr->name, "Subject") || + !g_ascii_strcasecmp(hdr->name, "From") || + !g_ascii_strcasecmp(hdr->name, "To") || + !g_ascii_strcasecmp(hdr->name, "Cc")) { + unfold_line(hdr->body); + } + + body = hdr->body; + while (g_ascii_isspace(*body)) + body++; + } else { + if (!g_ascii_strcasecmp(hdr->name, "Subject")) + body = msginfo->subject; + else if (!g_ascii_strcasecmp(hdr->name, "From")) + body = msginfo->from; + else if (!g_ascii_strcasecmp(hdr->name, "To")) + body = msginfo->to; + else if (!g_ascii_strcasecmp(hdr->name, "Cc")) { + unfold_line(hdr->body); + body = hdr->body; + while (g_ascii_isspace(*body)) + body++; + } else { + body = hdr->body; + while (g_ascii_isspace(*body)) + body++; + } + } + + if (body && *body != '\0') { + file_str = conv_codeset_strdup + (body, CS_INTERNAL, encoding); + fprintf(dest_fp, "%s: %s\n", hdr->name, + file_str ? file_str : body); + g_free(file_str); + } else { + fprintf(dest_fp, "%s: (none)\n", hdr->name); + } + } + + procheader_header_array_destroy(headers); +} + +void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline, + gboolean all_headers) +{ + gchar *prtmp; + + g_return_if_fail(msginfo != NULL); + + prtmp = g_strdup_printf("%s%cprinttmp-%08x.txt", + get_mime_tmp_dir(), G_DIR_SEPARATOR, + print_id++); + + if (procmsg_save_message_as_text(msginfo, prtmp, + conv_get_locale_charset_str(), + all_headers) == 0) + print_command_exec(prtmp, cmdline); + + g_free(prtmp); +} + +void procmsg_print_message_part(MsgInfo *msginfo, MimeInfo *partinfo, + const gchar *cmdline, gboolean all_headers) +{ + FILE *msgfp, *tmpfp, *prfp; + gchar *prtmp; + gchar buf[BUFFSIZE]; + + if ((msgfp = procmsg_open_message(msginfo)) == NULL) { + return; + } + + if ((tmpfp = procmime_get_text_content + (partinfo, msgfp, conv_get_locale_charset_str())) == NULL) { + fclose(msgfp); + return; + } + fclose(msgfp); + + prtmp = g_strdup_printf("%s%cprinttmp-%08x.txt", + get_mime_tmp_dir(), G_DIR_SEPARATOR, + print_id++); + if ((prfp = g_fopen(prtmp, "w")) == NULL) { + FILE_OP_ERROR(prtmp, "procmsg_print_message_part: fopen"); + g_free(prtmp); + fclose(tmpfp); + return; + } + + while (fgets(buf, sizeof(buf), tmpfp) != NULL) + fputs(buf, prfp); + + fclose(prfp); + fclose(tmpfp); + + print_command_exec(prtmp, cmdline); + + g_free(prtmp); +} + +gint procmsg_save_message_as_text(MsgInfo *msginfo, const gchar *dest, + const gchar *encoding, gboolean all_headers) +{ + MimeInfo *mimeinfo, *partinfo; + FILE *fp; + FILE *tmpfp; + FILE *destfp; + gchar buf[BUFFSIZE]; + gchar *part_str; + gint ret = 0; + + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(dest != NULL, -1); + + mimeinfo = procmime_scan_message(msginfo); + if (!mimeinfo) + return -1; + if ((fp = procmsg_open_message(msginfo)) == NULL) { + procmime_mimeinfo_free_all(mimeinfo); + return -1; + } + if ((destfp = g_fopen(dest, "w")) == NULL) { + fclose(fp); + procmime_mimeinfo_free_all(mimeinfo); + return -1; + } + procmsg_write_headers(msginfo, mimeinfo, fp, destfp, encoding, all_headers); + fputc('\n', destfp); + + partinfo = mimeinfo; + + while (partinfo != NULL) { + if (fseek(fp, partinfo->fpos, SEEK_SET) < 0) + break; + + if (partinfo->filename || partinfo->name) + g_snprintf(buf, sizeof(buf), "\n[%s %s (%s)]\n", + partinfo->filename ? partinfo->filename : + partinfo->name, + partinfo->content_type, + to_human_readable(partinfo->content_size)); + else + g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n", + partinfo->content_type, + to_human_readable(partinfo->content_size)); + part_str = conv_codeset_strdup(buf, CS_INTERNAL, encoding); + if (!part_str) + part_str = g_strdup(buf); + + if (partinfo->mime_type == MIME_TEXT || + partinfo->mime_type == MIME_TEXT_HTML) { + if (!partinfo->main && + partinfo->parent && + partinfo->parent->children != partinfo) { + fputs(part_str, destfp); + } + + if ((tmpfp = procmime_get_text_content(partinfo, fp, encoding)) == NULL) { + g_free(part_str); + break; + } + while (fgets(buf, sizeof(buf), tmpfp) != NULL) + fputs(buf, destfp); + + fclose(tmpfp); + } else if (partinfo->mime_type == MIME_MESSAGE_RFC822) { + fputs(part_str, destfp); + while (fgets(buf, sizeof(buf), fp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + procmsg_write_headers(msginfo, partinfo, fp, destfp, encoding, all_headers); + fputc('\n', destfp); + } else if (partinfo->mime_type != MIME_MULTIPART) { + fputs(part_str, destfp); + } + + g_free(part_str); + + if (partinfo->parent && partinfo->parent->content_type && + !g_ascii_strcasecmp(partinfo->parent->content_type, + "multipart/alternative")) + partinfo = partinfo->parent->next; + else + partinfo = procmime_mimeinfo_next(partinfo); + } + + if (fclose(destfp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + g_unlink(dest); + ret = -1; + } + + fclose(fp); + procmime_mimeinfo_free_all(mimeinfo); + + return ret; +} + +/** + * procmsg_concat_partial_messages: + * @mlist: list of MsgInfo* including message/partial messages. + * @file: output file name of concatenated message. + * + * Concatenate @mlist which consists of message/partial messages and + * output to @file. If @mlist has different partial id, the first one + * is used. + * + * Return value: 0 on success, or -1 if failed. + **/ +gint procmsg_concat_partial_messages(GSList *mlist, const gchar *file) +{ + static HeaderEntry hentry[] = {{"Content-Type:", NULL, FALSE}, + {NULL, NULL, FALSE}}; + FILE *fp; + gchar buf[BUFFSIZE]; + FILE *tmp_fp; + gchar *part_id = NULL; + gint total = 0; + MsgInfo *msg_array[100] = {NULL}; + MsgInfo *msginfo; + MimeInfo *mimeinfo; + GSList *cur; + gint i; + + g_return_val_if_fail(mlist != NULL, -1); + g_return_val_if_fail(file != NULL, -1); + + debug_print("procmsg_concat_partial_messages\n"); + + for (cur = mlist; cur != NULL; cur = cur->next) { + gint n = 0; + gint t = 0; + gchar *cur_id = NULL; + + msginfo = (MsgInfo *)cur->data; + + fp = procmsg_open_message_decrypted(msginfo, &mimeinfo); + if (!fp) + continue; + if (!mimeinfo->content_type || + g_ascii_strcasecmp(mimeinfo->content_type, "message/partial") != 0) + goto skip; + + rewind(fp); + if (procheader_get_one_field(buf, sizeof(buf), fp, hentry) == -1) + goto skip; + + procmime_scan_content_type_partial(buf + strlen(hentry[0].name), + &t, &cur_id, &n); + if (n == 0 || n > 100 || t > 100 || (t > 0 && n > t)) { + debug_print("bad partial number (%d/%d), skip\n", n, t); + g_free(cur_id); + goto skip; + } + + debug_print("partial: %d/%d id=%s\n", n, t, cur_id); + if (!part_id) + part_id = g_strdup(cur_id); + if (total == 0) + total = t; + + if ((t > 0 && total != t) || (total > 0 && n > total) || + strcmp(part_id, cur_id) != 0) { + debug_print("skip\n"); + g_free(cur_id); + goto skip; + } + + msg_array[n - 1] = msginfo; + + g_free(cur_id); +skip: + procmime_mimeinfo_free_all(mimeinfo); + fclose(fp); + } + + if (!part_id) { + debug_print("piece not found\n"); + return -1; + } + + debug_print("part_id = %s , total = %d\n", part_id, total); + g_free(part_id); + + if (total == 0) { + debug_print("total number not found\n"); + return -1; + } + + /* check if all pieces exist */ + for (i = 0; i < total; i++) { + if (msg_array[i] == NULL) { + debug_print("message part %d not exist\n", i + 1); + return -1; + } + } + + /* concatenate parts */ + if ((tmp_fp = g_fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + for (i = 0; i < total; i++) { + msginfo = msg_array[i]; + off_t out_size; + gint empty_line_size = 0; + + fp = procmsg_open_message_decrypted(msginfo, &mimeinfo); + if (!fp) { + g_warning("cannot open message part %d\n", i + 1); + fclose(tmp_fp); + g_unlink(file); + return -1; + } + + /* write out first headers */ + if (i == 0) { + rewind(fp); + while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) { + if (!g_ascii_strncasecmp(buf, "Content-", 8) || + !g_ascii_strncasecmp(buf, "Subject", 7) || + !g_ascii_strncasecmp(buf, "Message-ID", 10) || + !g_ascii_strncasecmp(buf, "Encrypted", 9) || + !g_ascii_strncasecmp(buf, "MIME-Version", 12)) + continue; + fputs(buf, tmp_fp); + fputs("\n", tmp_fp); + } + + while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) { + if (!g_ascii_strncasecmp(buf, "Content-", 8) || + !g_ascii_strncasecmp(buf, "Subject", 7) || + !g_ascii_strncasecmp(buf, "Message-ID", 10) || + !g_ascii_strncasecmp(buf, "Encrypted", 9) || + !g_ascii_strncasecmp(buf, "MIME-Version", 12)) { + fputs(buf, tmp_fp); + fputs("\n", tmp_fp); + } + } + + /* header-body separator */ + fputs("\n", tmp_fp); + } + + out_size = get_left_file_size(fp); + if (out_size < 0) { + g_warning("cannot tell left file size of part %d\n", i + 1); + procmime_mimeinfo_free_all(mimeinfo); + fclose(fp); + fclose(tmp_fp); + g_unlink(file); + return -1; + } + empty_line_size = get_last_empty_line_size(fp, out_size); + if (empty_line_size < 0) { + g_warning("cannot get last empty line size of part %d\n", i + 1); + procmime_mimeinfo_free_all(mimeinfo); + fclose(fp); + fclose(tmp_fp); + g_unlink(file); + return -1; + } + + if (append_file_part(fp, ftell(fp), out_size - empty_line_size, + tmp_fp) < 0) { + g_warning("write failed\n"); + procmime_mimeinfo_free_all(mimeinfo); + fclose(fp); + fclose(tmp_fp); + g_unlink(file); + return -1; + } + + procmime_mimeinfo_free_all(mimeinfo); + fclose(fp); + } + + fclose(tmp_fp); + + return 0; +} + +static gboolean procmsg_get_flags(FolderItem *item, gint num, + MsgPermFlags *flags) +{ + FILE *fp; + guint32 idata; + gint read_num; + MsgPermFlags perm_flags; + gboolean found = FALSE; + GSList *cur; + + if ((fp = procmsg_open_mark_file(item, DATA_READ)) == NULL) + return FALSE; + + while (fread(&idata, sizeof(idata), 1, fp) == 1) { + read_num = idata; + if (fread(&idata, sizeof(idata), 1, fp) != 1) + break; + perm_flags = idata; + if (read_num == num) { + *flags = perm_flags; + found = TRUE; + break; + } + } + + fclose(fp); + if (found) + return TRUE; + + for (cur = item->mark_queue; cur != NULL; cur = cur->next) { + MsgFlagInfo *flaginfo = (MsgFlagInfo *)cur->data; + + if (flaginfo->msgnum == num) { + *flags = flaginfo->flags.perm_flags; + found = TRUE; + break; + } + } + + return found; +} + +MsgInfo *procmsg_get_msginfo(FolderItem *item, gint num) +{ + MsgInfo *msginfo; + FolderType type; + + g_return_val_if_fail(item->folder != NULL, NULL); + + msginfo = folder_item_get_msginfo(item, num); + if (!msginfo) + return NULL; + + type = FOLDER_TYPE(item->folder); + if (type == F_MH || type == F_IMAP) { + if (item->stype == F_QUEUE) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED); + } else if (item->stype == F_DRAFT) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT); + } + } + if (type == F_IMAP) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_IMAP); + } else if (type == F_NEWS) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_NEWS); + } + + if (type == F_MH || type == F_NEWS) { + MsgPermFlags flags = 0; + if (procmsg_get_flags(item, num, &flags)) + msginfo->flags.perm_flags = flags; + } + + return msginfo; +} + +MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo) +{ + MsgInfo *newmsginfo; + + if (msginfo == NULL) return NULL; + + newmsginfo = g_new0(MsgInfo, 1); + +#define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb +#define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \ + g_strdup(msginfo->mmb) : NULL + + MEMBCOPY(msgnum); + MEMBCOPY(size); + MEMBCOPY(mtime); + MEMBCOPY(date_t); + + MEMBCOPY(flags); + + MEMBDUP(fromname); + + MEMBDUP(date); + MEMBDUP(from); + MEMBDUP(to); + MEMBDUP(cc); + MEMBDUP(newsgroups); + MEMBDUP(subject); + MEMBDUP(msgid); + MEMBDUP(inreplyto); + + MEMBCOPY(folder); + MEMBCOPY(to_folder); + + MEMBDUP(xface); + + MEMBDUP(file_path); + + if (msginfo->encinfo) { + newmsginfo->encinfo = g_new0(MsgEncryptInfo, 1); + MEMBDUP(encinfo->plaintext_file); + MEMBDUP(encinfo->sigstatus); + MEMBDUP(encinfo->sigstatus_full); + MEMBCOPY(encinfo->decryption_failed); + } + + return newmsginfo; +} + +MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo) +{ + MsgInfo *full_msginfo; + gchar *file; + + if (msginfo == NULL) return NULL; + + file = procmsg_get_message_file(msginfo); + if (!file) { + g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n"); + return NULL; + } + + full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE); + g_free(file); + if (!full_msginfo) return NULL; + + full_msginfo->msgnum = msginfo->msgnum; + full_msginfo->size = msginfo->size; + full_msginfo->mtime = msginfo->mtime; + full_msginfo->folder = msginfo->folder; + full_msginfo->to_folder = msginfo->to_folder; + + full_msginfo->file_path = g_strdup(msginfo->file_path); + + if (msginfo->encinfo) { + full_msginfo->encinfo = g_new0(MsgEncryptInfo, 1); + full_msginfo->encinfo->plaintext_file = + g_strdup(msginfo->encinfo->plaintext_file); + full_msginfo->encinfo->sigstatus = + g_strdup(msginfo->encinfo->sigstatus); + full_msginfo->encinfo->sigstatus_full = + g_strdup(msginfo->encinfo->sigstatus_full); + full_msginfo->encinfo->decryption_failed = + msginfo->encinfo->decryption_failed; + } + + return full_msginfo; +} + +gboolean procmsg_msginfo_equal(MsgInfo *msginfo_a, MsgInfo *msginfo_b) +{ + if (!msginfo_a || !msginfo_b) + return FALSE; + + if (msginfo_a == msginfo_b) + return TRUE; + + if (msginfo_a->folder == msginfo_b->folder && + msginfo_a->msgnum == msginfo_b->msgnum && + msginfo_a->size == msginfo_b->size && + msginfo_a->mtime == msginfo_b->mtime) + return TRUE; + + return FALSE; +} + +void procmsg_msginfo_free(MsgInfo *msginfo) +{ + if (msginfo == NULL) return; + + g_free(msginfo->xface); + + g_free(msginfo->fromname); + + g_free(msginfo->date); + g_free(msginfo->from); + g_free(msginfo->to); + g_free(msginfo->cc); + g_free(msginfo->newsgroups); + g_free(msginfo->subject); + g_free(msginfo->msgid); + g_free(msginfo->inreplyto); + + slist_free_strings(msginfo->references); + g_slist_free(msginfo->references); + + g_free(msginfo->file_path); + + if (msginfo->encinfo) { + g_free(msginfo->encinfo->plaintext_file); + g_free(msginfo->encinfo->sigstatus); + g_free(msginfo->encinfo->sigstatus_full); + g_free(msginfo->encinfo); + } + + g_free(msginfo); +} + +gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b) +{ + const MsgInfo *msginfo1 = a; + const MsgInfo *msginfo2 = b; + + if (!msginfo1 || !msginfo2) + return 0; + + return msginfo1->msgnum - msginfo2->msgnum; +} + +#define CMP_FUNC_DEF(func_name, val) \ +static gint func_name(gconstpointer a, gconstpointer b) \ +{ \ + const MsgInfo *msginfo1 = a; \ + const MsgInfo *msginfo2 = b; \ + gint ret; \ + \ + if (!msginfo1 || !msginfo2) \ + return 0; \ + \ + ret = (val); \ + if (ret == 0) \ + ret = msginfo1->date_t - msginfo2->date_t; \ + \ + return ret * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \ +} + +CMP_FUNC_DEF(procmsg_cmp_by_mark, + MSG_IS_MARKED(msginfo1->flags) - MSG_IS_MARKED(msginfo2->flags)) +CMP_FUNC_DEF(procmsg_cmp_by_unread, + MSG_IS_UNREAD(msginfo1->flags) - MSG_IS_UNREAD(msginfo2->flags)) +CMP_FUNC_DEF(procmsg_cmp_by_mime, + MSG_IS_MIME(msginfo1->flags) - MSG_IS_MIME(msginfo2->flags)) +CMP_FUNC_DEF(procmsg_cmp_by_label, + MSG_GET_COLORLABEL(msginfo1->flags) - + MSG_GET_COLORLABEL(msginfo2->flags)) +CMP_FUNC_DEF(procmsg_cmp_by_size, msginfo1->size - msginfo2->size) + +#undef CMP_FUNC_DEF +#define CMP_FUNC_DEF(func_name, val) \ +static gint func_name(gconstpointer a, gconstpointer b) \ +{ \ + const MsgInfo *msginfo1 = a; \ + const MsgInfo *msginfo2 = b; \ + \ + if (!msginfo1 || !msginfo2) \ + return 0; \ + \ + return (val) * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \ +} + +CMP_FUNC_DEF(procmsg_cmp_by_number, msginfo1->msgnum - msginfo2->msgnum) +CMP_FUNC_DEF(procmsg_cmp_by_date, msginfo1->date_t - msginfo2->date_t) + +#undef CMP_FUNC_DEF +#define CMP_FUNC_DEF(func_name, var_name) \ +static gint func_name(gconstpointer a, gconstpointer b) \ +{ \ + const MsgInfo *msginfo1 = a; \ + const MsgInfo *msginfo2 = b; \ + gint ret; \ + \ + if (!msginfo1->var_name) \ + return (msginfo2->var_name != NULL) * \ + (cmp_func_sort_type == SORT_ASCENDING ? -1 : 1);\ + if (!msginfo2->var_name) \ + return (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \ + \ + ret = g_ascii_strcasecmp \ + (msginfo1->var_name, msginfo2->var_name); \ + if (ret == 0) \ + ret = msginfo1->date_t - msginfo2->date_t; \ + \ + return ret * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \ +} + +CMP_FUNC_DEF(procmsg_cmp_by_from, fromname) +CMP_FUNC_DEF(procmsg_cmp_by_to, to) + +#undef CMP_FUNC_DEF + +static gint procmsg_cmp_by_subject(gconstpointer a, gconstpointer b) +{ + const MsgInfo *msginfo1 = a; + const MsgInfo *msginfo2 = b; + gint ret; + + if (!msginfo1->subject) + return (msginfo2->subject != NULL) * + (cmp_func_sort_type == SORT_ASCENDING ? -1 : 1); + if (!msginfo2->subject) + return (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); + + ret = subject_compare_for_sort(msginfo1->subject, msginfo2->subject); + if (ret == 0) + ret = msginfo1->date_t - msginfo2->date_t; + + return ret * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); +} diff --git a/libsylph/procmsg.h b/libsylph/procmsg.h new file mode 100644 index 0000000..77f0d8a --- /dev/null +++ b/libsylph/procmsg.h @@ -0,0 +1,355 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2013 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __PROCMSG_H__ +#define __PROCMSG_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +typedef struct _MsgInfo MsgInfo; +typedef struct _MsgFlags MsgFlags; +typedef struct _MsgFileInfo MsgFileInfo; +typedef struct _MsgEncryptInfo MsgEncryptInfo; + +#include "folder.h" +#include "procmime.h" +#include "utils.h" + +typedef enum +{ + DATA_READ, + DATA_WRITE, + DATA_APPEND +} DataOpenMode; + +#define MSG_NEW (1U << 0) +#define MSG_UNREAD (1U << 1) +#define MSG_MARKED (1U << 2) +#define MSG_DELETED (1U << 3) +#define MSG_REPLIED (1U << 4) +#define MSG_FORWARDED (1U << 5) + +#define MSG_CLABEL_SBIT (7) /* start bit of color label */ +#define MAKE_MSG_CLABEL(h, m, l) (((h) << (MSG_CLABEL_SBIT + 2)) | \ + ((m) << (MSG_CLABEL_SBIT + 1)) | \ + ((l) << (MSG_CLABEL_SBIT + 0))) + +#define MSG_CLABEL_NONE MAKE_MSG_CLABEL(0U, 0U, 0U) +#define MSG_CLABEL_1 MAKE_MSG_CLABEL(0U, 0U, 1U) +#define MSG_CLABEL_2 MAKE_MSG_CLABEL(0U, 1U, 0U) +#define MSG_CLABEL_3 MAKE_MSG_CLABEL(0U, 1U, 1U) +#define MSG_CLABEL_4 MAKE_MSG_CLABEL(1U, 0U, 0U) +#define MSG_CLABEL_5 MAKE_MSG_CLABEL(1U, 0U, 1U) +#define MSG_CLABEL_6 MAKE_MSG_CLABEL(1U, 1U, 0U) +#define MSG_CLABEL_7 MAKE_MSG_CLABEL(1U, 1U, 1U) + +#define MSG_CLABEL_ORANGE MSG_CLABEL_1 +#define MSG_CLABEL_RED MSG_CLABEL_2 +#define MSG_CLABEL_PINK MSG_CLABEL_3 +#define MSG_CLABEL_SKYBLUE MSG_CLABEL_4 +#define MSG_CLABEL_BLUE MSG_CLABEL_5 +#define MSG_CLABEL_GREEN MSG_CLABEL_6 +#define MSG_CLABEL_BROWN MSG_CLABEL_7 + +/* RESERVED */ +#define MSG_RESERVED (1U << 31) + +#define MSG_CLABEL_FLAG_MASK (MSG_CLABEL_7) + +typedef guint32 MsgPermFlags; + +#define MSG_MOVE (1U << 0) +#define MSG_COPY (1U << 1) +#define MSG_QUEUED (1U << 16) +#define MSG_DRAFT (1U << 17) +#define MSG_ENCRYPTED (1U << 18) +#define MSG_IMAP (1U << 19) +#define MSG_NEWS (1U << 20) +#define MSG_SIGNED (1U << 21) +#define MSG_MIME_HTML (1U << 26) +#define MSG_FLAG_CHANGED (1U << 27) +#define MSG_CACHED (1U << 28) +#define MSG_MIME (1U << 29) +#define MSG_INVALID (1U << 30) +#define MSG_RECEIVED (1U << 31) + +#define MSG_CACHED_FLAG_MASK (MSG_MIME|MSG_MIME_HTML) + +typedef guint32 MsgTmpFlags; + +#define MSG_SET_FLAGS(msg, flags) { (msg) |= (flags); } +#define MSG_UNSET_FLAGS(msg, flags) { (msg) &= ~(flags); } +#define MSG_SET_PERM_FLAGS(msg, flags) \ + MSG_SET_FLAGS((msg).perm_flags, flags) +#define MSG_SET_TMP_FLAGS(msg, flags) \ + MSG_SET_FLAGS((msg).tmp_flags, flags) +#define MSG_UNSET_PERM_FLAGS(msg, flags) \ + MSG_UNSET_FLAGS((msg).perm_flags, flags) +#define MSG_UNSET_TMP_FLAGS(msg, flags) \ + MSG_UNSET_FLAGS((msg).tmp_flags, flags) + +#define MSG_IS_NEW(msg) (((msg).perm_flags & MSG_NEW) != 0) +#define MSG_IS_UNREAD(msg) (((msg).perm_flags & MSG_UNREAD) != 0) +#define MSG_IS_MARKED(msg) (((msg).perm_flags & MSG_MARKED) != 0) +#define MSG_IS_DELETED(msg) (((msg).perm_flags & MSG_DELETED) != 0) +#define MSG_IS_REPLIED(msg) (((msg).perm_flags & MSG_REPLIED) != 0) +#define MSG_IS_FORWARDED(msg) (((msg).perm_flags & MSG_FORWARDED) != 0) + +#define MSG_GET_COLORLABEL(msg) (((msg).perm_flags & MSG_CLABEL_FLAG_MASK)) +#define MSG_GET_COLORLABEL_VALUE(msg) (MSG_GET_COLORLABEL(msg) >> MSG_CLABEL_SBIT) +#define MSG_SET_COLORLABEL_VALUE(msg, val) \ + MSG_SET_PERM_FLAGS(msg, ((((guint)(val)) & 7) << MSG_CLABEL_SBIT)) + +#define MSG_IS_MOVE(msg) (((msg).tmp_flags & MSG_MOVE) != 0) +#define MSG_IS_COPY(msg) (((msg).tmp_flags & MSG_COPY) != 0) + +#define MSG_IS_QUEUED(msg) (((msg).tmp_flags & MSG_QUEUED) != 0) +#define MSG_IS_DRAFT(msg) (((msg).tmp_flags & MSG_DRAFT) != 0) +#define MSG_IS_ENCRYPTED(msg) (((msg).tmp_flags & MSG_ENCRYPTED) != 0) +#define MSG_IS_IMAP(msg) (((msg).tmp_flags & MSG_IMAP) != 0) +#define MSG_IS_NEWS(msg) (((msg).tmp_flags & MSG_NEWS) != 0) +#define MSG_IS_SIGNED(msg) (((msg).tmp_flags & MSG_SIGNED) != 0) +#define MSG_IS_MIME_HTML(msg) (((msg).tmp_flags & MSG_MIME_HTML) != 0) +#define MSG_IS_FLAG_CHANGED(msg) (((msg).tmp_flags & MSG_FLAG_CHANGED) != 0) +#define MSG_IS_CACHED(msg) (((msg).tmp_flags & MSG_CACHED) != 0) +#define MSG_IS_MIME(msg) (((msg).tmp_flags & MSG_MIME) != 0) +#define MSG_IS_INVALID(msg) (((msg).tmp_flags & MSG_INVALID) != 0) +#define MSG_IS_RECEIVED(msg) (((msg).tmp_flags & MSG_RECEIVED) != 0) + +#define WRITE_CACHE_DATA_INT(n, fp) \ +{ \ + guint32 idata; \ + \ + idata = (guint32)n; \ + fwrite(&idata, sizeof(idata), 1, fp); \ +} + +#define WRITE_CACHE_DATA(data, fp) \ +{ \ + size_t len; \ + \ + if (data == NULL) { \ + len = 0; \ + WRITE_CACHE_DATA_INT(len, fp); \ + } else { \ + len = strlen(data); \ + WRITE_CACHE_DATA_INT(len, fp); \ + if (len > 0) \ + fwrite(data, len, 1, fp); \ + } \ +} + +struct _MsgFlags +{ + MsgPermFlags perm_flags; + MsgTmpFlags tmp_flags; +}; + +struct _MsgInfo +{ + guint msgnum; + gsize size; + stime_t mtime; + stime_t date_t; + + MsgFlags flags; + + gchar *fromname; + + gchar *date; + gchar *from; + gchar *to; + gchar *cc; + gchar *newsgroups; + gchar *subject; + gchar *msgid; + gchar *inreplyto; + + GSList *references; + + FolderItem *folder; + FolderItem *to_folder; + + gchar *xface; + + /* used only for temporary messages */ + gchar *file_path; + + /* used only for encrypted (and signed) messages */ + MsgEncryptInfo *encinfo; +}; + +struct _MsgFileInfo +{ + gchar *file; + MsgFlags *flags; +}; + +struct _MsgEncryptInfo +{ + gchar *plaintext_file; + gchar *sigstatus; + gchar *sigstatus_full; + gboolean decryption_failed; +}; + +typedef FILE * (*DecryptMessageFunc) (MsgInfo *msginfo, + MimeInfo **mimeinfo); + +GHashTable *procmsg_msg_hash_table_create (GSList *mlist); +void procmsg_msg_hash_table_append (GHashTable *msg_table, + GSList *mlist); +GHashTable *procmsg_to_folder_hash_table_create (GSList *mlist); + +gint procmsg_read_cache_data_str (FILE *fp, + gchar **str); + +GSList *procmsg_read_cache (FolderItem *item, + gboolean scan_file); +void procmsg_set_flags (GSList *mlist, + FolderItem *item); +void procmsg_mark_all_read (FolderItem *item); +GSList *procmsg_sort_msg_list (GSList *mlist, + FolderSortKey sort_key, + FolderSortType sort_type); +gint procmsg_get_last_num_in_msg_list(GSList *mlist); +void procmsg_msg_list_free (GSList *mlist); + +void procmsg_write_cache (MsgInfo *msginfo, + FILE *fp); +void procmsg_write_flags (MsgInfo *msginfo, + FILE *fp); +void procmsg_write_cache_list (FolderItem *item, + GSList *mlist); +void procmsg_write_flags_list (FolderItem *item, + GSList *mlist); +void procmsg_write_flags_for_multiple_folders + (GSList *mlist); + +void procmsg_flaginfo_list_free (GSList *flaglist); + +void procmsg_flush_mark_queue (FolderItem *item, + FILE *fp); +void procmsg_add_mark_queue (FolderItem *item, + gint num, + MsgFlags flags); +void procmsg_flush_cache_queue (FolderItem *item, + FILE *fp); +void procmsg_add_cache_queue (FolderItem *item, + gint num, + MsgInfo *msginfo); + +gboolean procmsg_flush_folder (FolderItem *item); +void procmsg_flush_folder_foreach (GHashTable *folder_table); + +void procmsg_add_flags (FolderItem *item, + gint num, + MsgFlags flags); + +void procmsg_get_mark_sum (FolderItem *item, + gint *new, + gint *unread, + gint *total, + gint *min, + gint *max, + gint first); + +FILE *procmsg_open_data_file (const gchar *file, + guint version, + DataOpenMode mode, + gchar *buf, + size_t buf_size); + +FILE *procmsg_open_cache_file (FolderItem *item, + DataOpenMode mode); +FILE *procmsg_open_mark_file (FolderItem *item, + DataOpenMode mode); + +void procmsg_clear_cache (FolderItem *item); +void procmsg_clear_mark (FolderItem *item); + +GNode *procmsg_get_thread_tree (GSList *mlist); +guint procmsg_get_thread_date (GNode *node); + +gint procmsg_move_messages (GSList *mlist); +gint procmsg_copy_messages (GSList *mlist); + +gint procmsg_add_messages_from_queue (FolderItem *dest, + GSList *mlist, + gboolean is_move); + +gchar *procmsg_get_message_file_path (MsgInfo *msginfo); +gchar *procmsg_get_message_file (MsgInfo *msginfo); +GSList *procmsg_get_message_file_list (GSList *mlist); +void procmsg_message_file_list_free (GSList *file_list); +FILE *procmsg_open_message (MsgInfo *msginfo); + +void procmsg_set_decrypt_message_func (DecryptMessageFunc func); +void procmsg_set_auto_decrypt_message (gboolean enabled); +FILE *procmsg_open_message_decrypted (MsgInfo *msginfo, + MimeInfo **mimeinfo); + +gboolean procmsg_msg_exist (MsgInfo *msginfo); + +gboolean procmsg_trash_messages_exist (void); +void procmsg_empty_trash (FolderItem *trash); +void procmsg_empty_all_trash (void); + +void procmsg_remove_all_cached_messages + (Folder *folder); + +gint procmsg_save_to_outbox (FolderItem *outbox, + const gchar *file); +void procmsg_print_message (MsgInfo *msginfo, + const gchar *cmdline, + gboolean all_headers); +void procmsg_print_message_part (MsgInfo *msginfo, + MimeInfo *partinfo, + const gchar *cmdline, + gboolean all_headers); + +gint procmsg_save_message_as_text (MsgInfo *msginfo, + const gchar *dest, + const gchar *encoding, + gboolean all_headers); + +gint procmsg_concat_partial_messages (GSList *mlist, + const gchar *file); + +MsgInfo *procmsg_get_msginfo (FolderItem *item, + gint num); + +MsgInfo *procmsg_msginfo_copy (MsgInfo *msginfo); +MsgInfo *procmsg_msginfo_get_full_info (MsgInfo *msginfo); +gboolean procmsg_msginfo_equal (MsgInfo *msginfo_a, + MsgInfo *msginfo_b); +void procmsg_msginfo_free (MsgInfo *msginfo); + +gint procmsg_cmp_msgnum_for_sort (gconstpointer a, + gconstpointer b); + +#endif /* __PROCMSG_H__ */ diff --git a/libsylph/quoted-printable.c b/libsylph/quoted-printable.c new file mode 100644 index 0000000..adcdb05 --- /dev/null +++ b/libsylph/quoted-printable.c @@ -0,0 +1,232 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +static gboolean get_hex_value(guchar *out, gchar c1, gchar c2); +static void get_hex_str(gchar *out, guchar ch); + +#define MAX_LINELEN 76 + +#define IS_LBREAK(p) \ + (*(p) == '\0' || *(p) == '\n' || (*(p) == '\r' && *((p) + 1) == '\n')) + +#define SOFT_LBREAK_IF_REQUIRED(n) \ + if (len + (n) > MAX_LINELEN || \ + (len + (n) == MAX_LINELEN && (!IS_LBREAK(inp + 1)))) { \ + *outp++ = '='; \ + *outp++ = '\n'; \ + len = 0; \ + } + +void qp_encode_line(gchar *out, const guchar *in) +{ + const guchar *inp = in; + gchar *outp = out; + guchar ch; + gint len = 0; + + while (*inp != '\0') { + ch = *inp; + + if (IS_LBREAK(inp)) { + *outp++ = '\n'; + len = 0; + if (*inp == '\r') + inp++; + inp++; + } else if (ch == '\t' || ch == ' ') { + if (IS_LBREAK(inp + 1)) { + SOFT_LBREAK_IF_REQUIRED(3); + *outp++ = '='; + get_hex_str(outp, ch); + outp += 2; + len += 3; + inp++; + } else { + SOFT_LBREAK_IF_REQUIRED(1); + *outp++ = *inp++; + len++; + } + } else if ((ch >= 33 && ch <= 60) || (ch >= 62 && ch <= 126)) { + SOFT_LBREAK_IF_REQUIRED(1); + *outp++ = *inp++; + len++; + } else { + SOFT_LBREAK_IF_REQUIRED(3); + *outp++ = '='; + get_hex_str(outp, ch); + outp += 2; + len += 3; + inp++; + } + } + + if (len > 0) + *outp++ = '\n'; + + *outp = '\0'; +} + +gint qp_decode_line(gchar *str) +{ + gchar *inp = str, *outp = str; + + while (*inp != '\0') { + if (*inp == '=') { + if (inp[1] && inp[2] && + get_hex_value((guchar *)outp, inp[1], inp[2]) + == TRUE) { + inp += 3; + } else if (inp[1] == '\0' || g_ascii_isspace(inp[1])) { + /* soft line break */ + break; + } else { + /* broken QP string */ + *outp = *inp++; + } + } else { + *outp = *inp++; + } + outp++; + } + + *outp = '\0'; + + return outp - str; +} + +gint qp_decode_q_encoding(guchar *out, const gchar *in, gint inlen) +{ + const gchar *inp = in; + guchar *outp = out; + + if (inlen < 0) + inlen = G_MAXINT; + + while (inp - in < inlen && *inp != '\0') { + if (*inp == '=' && inp + 3 - in <= inlen) { + if (get_hex_value(outp, inp[1], inp[2]) == TRUE) { + inp += 3; + } else { + *outp = *inp++; + } + } else if (*inp == '_') { + *outp = ' '; + inp++; + } else { + *outp = *inp++; + } + outp++; + } + + *outp = '\0'; + + return outp - out; +} + +gint qp_get_q_encoding_len(const guchar *str) +{ + const guchar *inp = str; + gint len = 0; + + while (*inp != '\0') { + if (*inp == 0x20) + len++; + else if (*inp == '=' || *inp == '?' || *inp == '_' || + *inp < 32 || *inp > 127 || g_ascii_isspace(*inp)) + len += 3; + else + len++; + + inp++; + } + + return len; +} + +void qp_q_encode(gchar *out, const guchar *in) +{ + const guchar *inp = in; + gchar *outp = out; + + while (*inp != '\0') { + if (*inp == 0x20) + *outp++ = '_'; + else if (*inp == '=' || *inp == '?' || *inp == '_' || + *inp < 32 || *inp > 127 || g_ascii_isspace(*inp)) { + *outp++ = '='; + get_hex_str(outp, *inp); + outp += 2; + } else + *outp++ = *inp; + + inp++; + } + + *outp = '\0'; +} + +#define HEX_TO_INT(val, hex) \ +{ \ + gchar c = hex; \ + \ + if ('0' <= c && c <= '9') { \ + val = c - '0'; \ + } else if ('a' <= c && c <= 'f') { \ + val = c - 'a' + 10; \ + } else if ('A' <= c && c <= 'F') { \ + val = c - 'A' + 10; \ + } else { \ + val = -1; \ + } \ +} + +static gboolean get_hex_value(guchar *out, gchar c1, gchar c2) +{ + gint hi, lo; + + HEX_TO_INT(hi, c1); + HEX_TO_INT(lo, c2); + + if (hi == -1 || lo == -1) + return FALSE; + + *out = (hi << 4) + lo; + return TRUE; +} + +#define INT_TO_HEX(hex, val) \ +{ \ + if ((val) < 10) \ + hex = '0' + (val); \ + else \ + hex = 'A' + (val) - 10; \ +} + +static void get_hex_str(gchar *out, guchar ch) +{ + gchar hex; + + INT_TO_HEX(hex, ch >> 4); + *out++ = hex; + INT_TO_HEX(hex, ch & 0x0f); + *out++ = hex; +} diff --git a/libsylph/quoted-printable.h b/libsylph/quoted-printable.h new file mode 100644 index 0000000..b6f11f7 --- /dev/null +++ b/libsylph/quoted-printable.h @@ -0,0 +1,36 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __QUOTED_PRINTABLE_H__ +#define __QUOTED_PRINTABLE_H__ + +#include + +void qp_encode_line (gchar *out, + const guchar *in); +gint qp_decode_line (gchar *str); + +gint qp_decode_q_encoding (guchar *out, + const gchar *in, + gint inlen); +gint qp_get_q_encoding_len (const guchar *str); +void qp_q_encode (gchar *out, + const guchar *in); + +#endif /* __QUOTED_PRINTABLE_H__ */ diff --git a/libsylph/recv.c b/libsylph/recv.c new file mode 100644 index 0000000..6ed4db2 --- /dev/null +++ b/libsylph/recv.c @@ -0,0 +1,245 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2014 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include + +#include "recv.h" +#include "socket.h" +#include "utils.h" + +static RecvUIFunc recv_ui_func; +static gpointer recv_ui_func_data; + + +gchar *recv_bytes(SockInfo *sock, glong size) +{ + gchar *buf; + glong count = 0; + + if (size == 0) + return NULL; + + buf = g_malloc(size + 1); + + do { + gint read_count; + + read_count = sock_read(sock, buf + count, + MIN(BUFFSIZE, size - count)); + if (read_count <= 0) { + g_free(buf); + return NULL; + } + count += read_count; + } while (count < size); + + buf[size] = '\0'; + + return buf; +} + +gint recv_write_to_file(SockInfo *sock, const gchar *filename) +{ + FILE *fp; + gint ret; + + g_return_val_if_fail(filename != NULL, -1); + + if ((fp = g_fopen(filename, "wb")) == NULL) { + FILE_OP_ERROR(filename, "fopen"); + recv_write(sock, NULL); + return -1; + } + + if (change_file_mode_rw(fp, filename) < 0) + FILE_OP_ERROR(filename, "chmod"); + + if ((ret = recv_write(sock, fp)) < 0) { + fclose(fp); + g_unlink(filename); + return ret; + } + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(filename, "fclose"); + g_unlink(filename); + return -1; + } + + return 0; +} + +gint recv_bytes_write_to_file(SockInfo *sock, glong size, const gchar *filename) +{ + FILE *fp; + gint ret; + + g_return_val_if_fail(filename != NULL, -1); + + if ((fp = g_fopen(filename, "wb")) == NULL) { + FILE_OP_ERROR(filename, "fopen"); + return recv_bytes_write(sock, size, NULL); + } + + if (change_file_mode_rw(fp, filename) < 0) + FILE_OP_ERROR(filename, "chmod"); + + if ((ret = recv_bytes_write(sock, size, fp)) < 0) { + fclose(fp); + g_unlink(filename); + return ret; + } + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(filename, "fclose"); + g_unlink(filename); + return -1; + } + + return 0; +} + +gint recv_write(SockInfo *sock, FILE *fp) +{ + gchar buf[BUFFSIZE]; + gint len; + gint count = 0; + gint bytes = 0; + gchar *p; + GTimeVal tv_prev, tv_cur; + + g_get_current_time(&tv_prev); + + for (;;) { + if (sock_gets(sock, buf, sizeof(buf)) < 0) { + g_warning(_("error occurred while retrieving data.\n")); + return -2; + } + + len = strlen(buf); + if (len > 1 && buf[0] == '.' && buf[1] == '\r') { + if (recv_ui_func) + recv_ui_func(sock, count, bytes, + recv_ui_func_data); + break; + } + count++; + bytes += len; + + if (recv_ui_func) { + g_get_current_time(&tv_cur); + /* if elapsed time from previous update is greater + than 50msec, update UI */ + if (tv_cur.tv_sec - tv_prev.tv_sec > 0 || + tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) { + gboolean ret; + ret = recv_ui_func(sock, count, bytes, + recv_ui_func_data); + if (ret == FALSE) return -1; + g_get_current_time(&tv_prev); + } + } + + p = buf; + + if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') { + buf[len - 2] = '\n'; + buf[len - 1] = '\0'; + } + + if (buf[0] == '.' && buf[1] == '.') + p++; + else if (!strncmp(buf, ">From ", 6)) + p++; + + if (fp && fputs(p, fp) == EOF) { + perror("fputs"); + g_warning(_("Can't write to file.\n")); + fp = NULL; + } + } + + if (!fp) return -1; + + return 0; +} + +gint recv_bytes_write(SockInfo *sock, glong size, FILE *fp) +{ + gchar *buf; + gchar *prev, *cur; + + if (size == 0) + return 0; + + buf = recv_bytes(sock, size); + if (!buf) + return -2; + + /* +------------------+----------------+--------------------------+ * + * ^buf ^prev ^cur buf+size-1^ */ + + prev = buf; + while ((cur = memchr(prev, '\r', size - (prev - buf))) != NULL) { + if (cur == buf + size - 1) break; + + if (fp && (fwrite(prev, sizeof(gchar), cur - prev, fp) == EOF || + fwrite("\n", sizeof(gchar), 1, fp) == EOF)) { + perror("fwrite"); + g_warning(_("Can't write to file.\n")); + fp = NULL; + } + + if (*(cur + 1) == '\n') + prev = cur + 2; + else + prev = cur + 1; + + if (prev - buf >= size) break; + } + + if (prev - buf < size && fp && + fwrite(prev, sizeof(gchar), size - (prev - buf), fp) == EOF) { + perror("fwrite"); + g_warning(_("Can't write to file.\n")); + fp = NULL; + } + + g_free(buf); + + if (!fp) return -1; + + return 0; +} + +void recv_set_ui_func(RecvUIFunc func, gpointer data) +{ + recv_ui_func = func; + recv_ui_func_data = data; +} diff --git a/libsylph/recv.h b/libsylph/recv.h new file mode 100644 index 0000000..bc31b9c --- /dev/null +++ b/libsylph/recv.h @@ -0,0 +1,49 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __RECV_H__ +#define __RECV_H__ + +#include + +#include "socket.h" + +typedef gboolean (*RecvUIFunc) (SockInfo *sock, + gint count, + gint read_bytes, + gpointer data); + +gchar *recv_bytes (SockInfo *sock, + glong size); + +gint recv_write_to_file (SockInfo *sock, + const gchar *filename); +gint recv_bytes_write_to_file (SockInfo *sock, + glong size, + const gchar *filename); +gint recv_write (SockInfo *sock, + FILE *fp); +gint recv_bytes_write (SockInfo *sock, + glong size, + FILE *fp); + +void recv_set_ui_func (RecvUIFunc func, + gpointer data); + +#endif /* __RECV_H__ */ diff --git a/libsylph/session.c b/libsylph/session.c new file mode 100644 index 0000000..08a493e --- /dev/null +++ b/libsylph/session.c @@ -0,0 +1,1320 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2012 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "session.h" +#include "utils.h" + +typedef struct _SessionPrivData SessionPrivData; + +struct _SessionPrivData { + Session *session; + SocksInfo *socks_info; + SessionErrorValue error_val; + gpointer data; +}; + +static GList *priv_list = NULL; + +static SessionPrivData *session_get_priv(Session *session); + +static gint session_connect_cb (SockInfo *sock, + gpointer data); +static gint session_close (Session *session); + +static gboolean session_timeout_cb (gpointer data); + +#ifdef G_OS_WIN32 +static gboolean session_ping_cb (gpointer data); +#endif + +static gboolean session_recv_msg_idle_cb (gpointer data); +static gboolean session_recv_data_idle_cb (gpointer data); + +static gboolean session_recv_data_as_file_idle_cb (gpointer data); + +static gboolean session_read_msg_cb (SockInfo *source, + GIOCondition condition, + gpointer data); +static gboolean session_read_data_cb (SockInfo *source, + GIOCondition condition, + gpointer data); + +static gboolean session_read_data_as_file_cb (SockInfo *source, + GIOCondition condition, + gpointer data); + +static gboolean session_write_msg_cb (SockInfo *source, + GIOCondition condition, + gpointer data); +static gboolean session_write_data_cb (SockInfo *source, + GIOCondition condition, + gpointer data); + + +void session_init(Session *session) +{ + SessionPrivData *priv; + + session->type = SESSION_UNKNOWN; + session->sock = NULL; + session->server = NULL; + session->port = 0; +#if USE_SSL + session->ssl_type = SSL_NONE; +#endif + session->nonblocking = TRUE; + session->state = SESSION_READY; + session->last_access_time = time(NULL); + + g_get_current_time(&session->tv_prev); + + session->conn_id = 0; + + session->io_tag = 0; + + session->read_buf_p = session->read_buf; + session->read_buf_len = 0; + + session->read_msg_buf = g_string_sized_new(1024); + session->read_data_buf = g_byte_array_new(); + session->read_data_terminator = NULL; + + session->read_data_fp = NULL; + session->read_data_pos = 0; + + session->preread_len = 0; + + session->write_buf = NULL; + session->write_buf_p = NULL; + session->write_buf_len = 0; + + session->write_data_fp = NULL; + session->write_data_pos = 0; + session->write_data_len = 0; + + session->timeout_tag = 0; + session->timeout_interval = 0; + + session->idle_tag = 0; + session->ping_tag = 0; + + session->data = NULL; + + priv = g_new0(SessionPrivData, 1); + priv->session = session; + priv->socks_info = NULL; + priv->error_val = SESSION_ERROR_OK; + priv_list = g_list_prepend(priv_list, priv); +} + +static SessionPrivData *session_get_priv(Session *session) +{ + SessionPrivData *priv; + GList *cur; + + g_return_val_if_fail(session != NULL, NULL); + + for (cur = priv_list; cur != NULL; cur = cur->next) { + priv = (SessionPrivData *)cur->data; + if (priv->session == session) + return priv; + } + + return NULL; +} + +gint session_connect(Session *session, const gchar *server, gushort port) +{ + return session_connect_full(session, server, port, NULL); +} + +gint session_connect_full(Session *session, const gchar *server, gushort port, + SocksInfo *socks_info) +{ + SessionPrivData *priv; +#ifndef G_OS_UNIX + SockInfo *sock = NULL; +#endif + g_return_val_if_fail(session != NULL, -1); + g_return_val_if_fail(server != NULL, -1); + g_return_val_if_fail(port > 0, -1); + + priv = session_get_priv(session); + g_return_val_if_fail(priv != NULL, -1); + priv->socks_info = socks_info; + + if (session->server != server) { + g_free(session->server); + session->server = g_strdup(server); + } + session->port = port; + + if (socks_info) { + g_return_val_if_fail(socks_info->proxy_host != NULL, -1); + server = socks_info->proxy_host; + port = socks_info->proxy_port; + } + +#ifdef G_OS_UNIX + session->conn_id = sock_connect_async(server, port, session_connect_cb, + session); + if (session->conn_id < 0) { + g_warning("can't connect to server."); + session->state = SESSION_ERROR; + priv->error_val = SESSION_ERROR_CONNFAIL; + return -1; + } +#elif USE_THREADS + session->conn_id = sock_connect_async_thread(server, port); + if (session->conn_id < 0) { + g_warning("can't connect to server."); + session->state = SESSION_ERROR; + priv->error_val = SESSION_ERROR_CONNFAIL; + return -1; + } + if (sock_info_connect_async_thread_wait(session->conn_id, &sock) < 0) { + session_connect_cb(sock, session); + if (sock) + sock_close(sock); + return -1; + } +#else /* !USE_THREADS */ + sock = sock_new(server, port); + if (sock_info_connect(sock) < 0) { + session_connect_cb(sock, session); + sock_close(sock); + return -1; + } +#endif + +#ifdef G_OS_UNIX + return 0; +#else + return session_connect_cb(sock, session); +#endif +} + +static gint session_connect_cb(SockInfo *sock, gpointer data) +{ + Session *session = SESSION(data); + SessionPrivData *priv; + + priv = session_get_priv(session); + session->conn_id = 0; + + if (!sock) { + g_warning("can't connect to server."); + session->state = SESSION_ERROR; + priv->error_val = SESSION_ERROR_CONNFAIL; + return -1; + } + if (sock->state == CONN_LOOKUPFAILED) { + g_warning("DNS lookup failed."); + session->state = SESSION_ERROR; + priv->error_val = SESSION_ERROR_LOOKUP; + return -1; + } else if (sock->state != CONN_ESTABLISHED) { + g_warning("can't connect to server (ConnectionState: %d).", + sock->state); + session->state = SESSION_ERROR; + priv->error_val = SESSION_ERROR_CONNFAIL; + return -1; + } + + session->sock = sock; + + if (priv->socks_info) { + sock_set_nonblocking_mode(sock, FALSE); + if (socks_connect(sock, session->server, session->port, + priv->socks_info) < 0) { + g_warning("can't establish SOCKS connection."); + session->state = SESSION_ERROR; + priv->error_val = SESSION_ERROR_CONNFAIL; + return -1; + } + } + +#if USE_SSL + if (session->ssl_type == SSL_TUNNEL) { + sock_set_nonblocking_mode(sock, FALSE); + if (!ssl_init_socket(sock)) { + g_warning("can't initialize SSL."); + session->state = SESSION_ERROR; + priv->error_val = SESSION_ERROR_SOCKET; + return -1; + } + } +#endif + + debug_print("session (%p): connected\n", session); + + sock_set_nonblocking_mode(sock, session->nonblocking); + + session->state = SESSION_RECV; + priv->error_val = SESSION_ERROR_OK; + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_msg_cb, + session); + +#ifdef G_OS_WIN32 + session->ping_tag = g_timeout_add_full(G_PRIORITY_LOW, 1000, session_ping_cb, session, NULL); +#endif + + return 0; +} + +gint session_disconnect(Session *session) +{ + session_close(session); + return 0; +} + +void session_destroy(Session *session) +{ + SessionPrivData *priv; + + g_return_if_fail(session != NULL); + g_return_if_fail(session->destroy != NULL); + + session_close(session); + session->destroy(session); + g_free(session->server); + g_string_free(session->read_msg_buf, TRUE); + g_byte_array_free(session->read_data_buf, TRUE); + g_free(session->read_data_terminator); + if (session->read_data_fp) + fclose(session->read_data_fp); + g_free(session->write_buf); + + priv = session_get_priv(session); + if (priv) { + priv_list = g_list_remove(priv_list, priv); + socks_info_free(priv->socks_info); + g_free(priv); + } + + debug_print("session (%p): destroyed\n", session); + + g_free(session); +} + +gboolean session_is_connected(Session *session) +{ + return (session->state == SESSION_READY || + session->state == SESSION_SEND || + session->state == SESSION_RECV); +} + +SessionErrorValue session_get_error(Session *session) +{ + SessionPrivData *priv; + + priv = session_get_priv(session); + if (priv) + return priv->error_val; + else + return SESSION_ERROR_ERROR; +} + +void session_set_access_time(Session *session) +{ + session->last_access_time = time(NULL); +} + +void session_set_timeout(Session *session, guint interval) +{ + if (session->timeout_tag > 0) + g_source_remove(session->timeout_tag); + + session->timeout_interval = interval; + if (interval > 0) + session->timeout_tag = + g_timeout_add_full(G_PRIORITY_LOW, interval, session_timeout_cb, session, NULL); + else + session->timeout_tag = 0; +} + +static gboolean session_timeout_cb(gpointer data) +{ + Session *session = SESSION(data); + SessionPrivData *priv; + + g_warning("session timeout.\n"); + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + session->timeout_tag = 0; + session->state = SESSION_TIMEOUT; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_TIMEOUT; + + return FALSE; +} + +#ifdef G_OS_WIN32 +/* hack for state machine freeze problem in GLib >= 2.8.x */ +static gboolean session_ping_cb(gpointer data) +{ + Session *session = SESSION(data); + SockInfo *sock = session->sock; + + if (!session_is_connected(session)) + return FALSE; + + if (session->io_tag > 0 && sock && sock->callback) { + GTimeVal tv_cur, tv_result; + + g_get_current_time(&tv_cur); + tv_result.tv_sec = tv_cur.tv_sec - session->tv_prev.tv_sec; + tv_result.tv_usec = tv_cur.tv_usec - session->tv_prev.tv_usec; + if (tv_result.tv_usec < 0) { + tv_result.tv_sec--; + tv_result.tv_usec += G_USEC_PER_SEC; + } + if (tv_result.tv_sec * G_USEC_PER_SEC + tv_result.tv_usec > + G_USEC_PER_SEC) { + SockFlags save_flags; + + debug_print("state machine freeze for 1 second detected, forcing dispatch.\n"); + save_flags = sock->flags; + SOCK_UNSET_FLAGS(sock->flags, SYL_SOCK_CHECK_IO); + sock->callback(sock, sock->condition, sock->data); + sock->flags = save_flags; + } + } + + return TRUE; +} +#endif + +void session_set_recv_message_notify(Session *session, + RecvMsgNotify notify_func, gpointer data) +{ + session->recv_msg_notify = notify_func; + session->recv_msg_notify_data = data; +} + +void session_set_recv_data_progressive_notify + (Session *session, + RecvDataProgressiveNotify notify_func, + gpointer data) +{ + session->recv_data_progressive_notify = notify_func, + session->recv_data_progressive_notify_data = data; +} + +void session_set_recv_data_notify(Session *session, RecvDataNotify notify_func, + gpointer data) +{ + session->recv_data_notify = notify_func; + session->recv_data_notify_data = data; +} + +void session_set_send_data_progressive_notify + (Session *session, + SendDataProgressiveNotify notify_func, + gpointer data) +{ + session->send_data_progressive_notify = notify_func; + session->send_data_progressive_notify_data = data; +} + +void session_set_send_data_notify(Session *session, SendDataNotify notify_func, + gpointer data) +{ + session->send_data_notify = notify_func; + session->send_data_notify_data = data; +} + +static gint session_close(Session *session) +{ + g_return_val_if_fail(session != NULL, -1); + +#ifdef G_OS_UNIX + if (session->conn_id > 0) { + sock_connect_async_cancel(session->conn_id); + session->conn_id = 0; + debug_print("session (%p): connection cancelled\n", session); + } +#endif + + session_set_timeout(session, 0); + + if (session->idle_tag > 0) { + g_source_remove(session->idle_tag); + session->idle_tag = 0; + } + +#ifdef G_OS_WIN32 + if (session->ping_tag > 0) { + g_source_remove(session->ping_tag); + session->ping_tag = 0; + } +#endif + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + if (session->sock) { + sock_close(session->sock); + session->sock = NULL; + session->state = SESSION_DISCONNECTED; + debug_print("session (%p): closed\n", session); + } + + return 0; +} + +#if USE_SSL +gint session_start_tls(Session *session) +{ + gboolean nb_mode; + + nb_mode = sock_is_nonblocking_mode(session->sock); + + sock_set_nonblocking_mode(session->sock, FALSE); + + if (!ssl_init_socket_with_method(session->sock, SSL_METHOD_TLSv1)) { + g_warning("can't start TLS session.\n"); + if (nb_mode) + sock_set_nonblocking_mode(session->sock, TRUE); + return -1; + } + + sock_set_nonblocking_mode(session->sock, session->nonblocking); + + return 0; +} +#endif + +gint session_send_msg(Session *session, SessionMsgType type, const gchar *msg) +{ + gboolean ret; + + g_return_val_if_fail(session->sock != NULL, -1); + g_return_val_if_fail(session->write_buf == NULL, -1); + g_return_val_if_fail(msg != NULL, -1); + g_return_val_if_fail(msg[0] != '\0', -1); + + session->state = SESSION_SEND; + session->write_buf = g_strconcat(msg, "\r\n", NULL); + session->write_buf_p = session->write_buf; + session->write_buf_len = strlen(msg) + 2; + + ret = session_write_msg_cb(session->sock, G_IO_OUT, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_OUT, + session_write_msg_cb, session); + else if (session->state == SESSION_ERROR) + return -1; + + return 0; +} + +gint session_recv_msg(Session *session) +{ + g_return_val_if_fail(session->sock != NULL, -1); + g_return_val_if_fail(session->read_msg_buf->len == 0, -1); + + session->state = SESSION_RECV; + + if (session->read_buf_len > 0) + session->idle_tag = g_idle_add(session_recv_msg_idle_cb, + session); + else + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_msg_cb, session); + + return 0; +} + +static gboolean session_recv_msg_idle_cb(gpointer data) +{ + Session *session = SESSION(data); + gboolean ret; + +#if GLIB_CHECK_VERSION(2, 12, 0) + if (g_source_is_destroyed(g_main_current_source())) + return FALSE; +#endif + + session->idle_tag = 0; + ret = session_read_msg_cb(session->sock, G_IO_IN, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_msg_cb, session); + + return FALSE; +} + +gint session_send_data(Session *session, FILE *data_fp, guint size) +{ + gboolean ret; + + g_return_val_if_fail(session->sock != NULL, -1); + g_return_val_if_fail(session->write_data_fp == NULL, -1); + g_return_val_if_fail(data_fp != NULL, -1); + g_return_val_if_fail(size != 0, -1); + + session->state = SESSION_SEND; + + session->write_data_fp = data_fp; + session->write_data_pos = 0; + session->write_data_len = size; + g_get_current_time(&session->tv_prev); + +#ifdef G_OS_WIN32 + sock_set_nonblocking_mode(session->sock, FALSE); +#endif + + ret = session_write_data_cb(session->sock, G_IO_OUT, session); + + if (ret == TRUE) +#ifdef G_OS_WIN32 + session->io_tag = sock_add_watch_poll(session->sock, G_IO_OUT, + session_write_data_cb, + session); +#else + session->io_tag = sock_add_watch(session->sock, G_IO_OUT, + session_write_data_cb, + session); +#endif + else if (session->state == SESSION_ERROR) + return -1; + + return 0; +} + +gint session_recv_data(Session *session, guint size, const gchar *terminator) +{ + g_return_val_if_fail(session->sock != NULL, -1); + g_return_val_if_fail(session->read_data_buf->len == 0, -1); + + session->state = SESSION_RECV; + + g_free(session->read_data_terminator); + session->read_data_terminator = g_strdup(terminator); + g_get_current_time(&session->tv_prev); + + if (session->read_buf_len > 0) + session->idle_tag = g_idle_add(session_recv_data_idle_cb, + session); + else + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_data_cb, session); + + return 0; +} + +static gboolean session_recv_data_idle_cb(gpointer data) +{ + Session *session = SESSION(data); + gboolean ret; + +#if GLIB_CHECK_VERSION(2, 12, 0) + if (g_source_is_destroyed(g_main_current_source())) + return FALSE; +#endif + + session->idle_tag = 0; + ret = session_read_data_cb(session->sock, G_IO_IN, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_data_cb, session); + + return FALSE; +} + +gint session_recv_data_as_file(Session *session, guint size, + const gchar *terminator) +{ + g_return_val_if_fail(session->sock != NULL, -1); + g_return_val_if_fail(session->read_data_pos == 0, -1); + g_return_val_if_fail(session->read_data_fp == NULL, -1); + + session->state = SESSION_RECV; + + g_free(session->read_data_terminator); + session->read_data_terminator = g_strdup(terminator); + g_get_current_time(&session->tv_prev); + + session->read_data_fp = my_tmpfile(); + if (!session->read_data_fp) { + FILE_OP_ERROR("session_recv_data_as_file", "my_tmpfile"); + return -1; + } + + if (session->read_buf_len > 0) + session->idle_tag = + g_idle_add(session_recv_data_as_file_idle_cb, session); + else + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_data_as_file_cb, + session); + + return 0; +} + +static gboolean session_recv_data_as_file_idle_cb(gpointer data) +{ + Session *session = SESSION(data); + gboolean ret; + +#if GLIB_CHECK_VERSION(2, 12, 0) + if (g_source_is_destroyed(g_main_current_source())) + return FALSE; +#endif + + session->idle_tag = 0; + ret = session_read_data_as_file_cb(session->sock, G_IO_IN, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_data_as_file_cb, + session); + + return FALSE; +} + +static gboolean session_read_msg_cb(SockInfo *source, GIOCondition condition, + gpointer data) +{ + Session *session = SESSION(data); + SessionPrivData *priv; + gchar buf[SESSION_BUFFSIZE]; + gint line_len; + gchar *newline; + gchar *msg; + gint ret; + + g_return_val_if_fail(condition == G_IO_IN, FALSE); + + if (session->read_buf_len == 0) { + gint read_len; + + read_len = sock_read(session->sock, session->read_buf, + SESSION_BUFFSIZE - 1); + + if (read_len == 0) { + g_warning("sock_read: received EOF\n"); + session->state = SESSION_EOF; + return FALSE; + } + + if (read_len < 0) { + switch (errno) { + case EAGAIN: + return TRUE; + default: + g_warning("%s: sock_read: %s\n", G_STRFUNC, g_strerror(errno)); + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_SOCKET; + return FALSE; + } + } + + session->read_buf_len = read_len; + } + + session_set_timeout(session, session->timeout_interval); + + if ((newline = memchr(session->read_buf_p, '\n', session->read_buf_len)) + != NULL) + line_len = newline - session->read_buf_p + 1; + else + line_len = session->read_buf_len; + + if (line_len == 0) + return TRUE; + + memcpy(buf, session->read_buf_p, line_len); + buf[line_len] = '\0'; + + g_string_append(session->read_msg_buf, buf); + + session->read_buf_len -= line_len; + if (session->read_buf_len == 0) + session->read_buf_p = session->read_buf; + else + session->read_buf_p += line_len; + + /* incomplete read */ + if (buf[line_len - 1] != '\n') + return TRUE; + + /* complete */ + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + /* callback */ + msg = g_strdup(session->read_msg_buf->str); + strretchomp(msg); + g_string_truncate(session->read_msg_buf, 0); + + ret = session->recv_msg(session, msg); + if (session->recv_msg_notify) + session->recv_msg_notify(session, msg, + session->recv_msg_notify_data); + + g_free(msg); + + if (ret < 0) { + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_SOCKET; + } + + return FALSE; +} + +static gboolean session_read_data_cb(SockInfo *source, GIOCondition condition, + gpointer data) +{ + Session *session = SESSION(data); + SessionPrivData *priv; + GByteArray *data_buf; + gint terminator_len; + gboolean complete = FALSE; + guint data_len; + gint ret; + + g_return_val_if_fail(condition == G_IO_IN, FALSE); + + if (session->read_buf_len == 0) { + gint read_len; + + read_len = sock_read(session->sock, session->read_buf, + SESSION_BUFFSIZE); + + if (read_len == 0) { + g_warning("sock_read: received EOF\n"); + session->state = SESSION_EOF; + return FALSE; + } + + if (read_len < 0) { + switch (errno) { + case EAGAIN: + return TRUE; + default: + g_warning("%s: sock_read: %s\n", G_STRFUNC, g_strerror(errno)); + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_SOCKET; + return FALSE; + } + } + + session->read_buf_len = read_len; + } + + session_set_timeout(session, session->timeout_interval); + + data_buf = session->read_data_buf; + terminator_len = strlen(session->read_data_terminator); + + if (session->read_buf_len == 0) + return TRUE; + + g_byte_array_append(data_buf, (guchar *)session->read_buf_p, + session->read_buf_len); + + session->read_buf_len = 0; + session->read_buf_p = session->read_buf; + + /* check if data is terminated */ + if (data_buf->len >= terminator_len) { + if (memcmp(data_buf->data, session->read_data_terminator, + terminator_len) == 0) + complete = TRUE; + else if (data_buf->len >= terminator_len + 2 && + memcmp(data_buf->data + data_buf->len - + (terminator_len + 2), "\r\n", 2) == 0 && + memcmp(data_buf->data + data_buf->len - + terminator_len, session->read_data_terminator, + terminator_len) == 0) + complete = TRUE; + } + + /* incomplete read */ + if (!complete) { + GTimeVal tv_cur; + + g_get_current_time(&tv_cur); + if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 || + tv_cur.tv_usec - session->tv_prev.tv_usec > + UI_REFRESH_INTERVAL) { + if (session->recv_data_progressive_notify) + session->recv_data_progressive_notify + (session, data_buf->len, 0, + session->recv_data_progressive_notify_data); + g_get_current_time(&session->tv_prev); + } + return TRUE; + } + + /* complete */ + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + data_len = data_buf->len - terminator_len; + + /* callback */ + ret = session->recv_data_finished(session, (guchar *)data_buf->data, + data_len); + + g_byte_array_set_size(data_buf, 0); + + if (session->recv_data_notify) + session->recv_data_notify(session, data_len, + session->recv_data_notify_data); + + if (ret < 0) { + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_SOCKET; + } + + return FALSE; +} + +#define READ_BUF_LEFT() \ + (SESSION_BUFFSIZE - (session->read_buf_p - session->read_buf) - \ + session->read_buf_len) +#define PREREAD_SIZE 8 + +static gboolean session_read_data_as_file_cb(SockInfo *source, + GIOCondition condition, + gpointer data) +{ + Session *session = SESSION(data); + SessionPrivData *priv; + gint terminator_len; + gchar *data_begin_p; + gint buf_data_len; + gboolean complete = FALSE; + gint read_len; + gint write_len; + gint ret; + + g_return_val_if_fail(condition == G_IO_IN, FALSE); + + if (session->read_buf_len == 0) { + read_len = sock_read(session->sock, session->read_buf_p, + READ_BUF_LEFT()); + + if (read_len == 0) { + g_warning("sock_read: received EOF\n"); + session->state = SESSION_EOF; + return FALSE; + } + + if (read_len < 0) { + switch (errno) { + case EAGAIN: + return TRUE; + default: + g_warning("%s: sock_read: %s\n", G_STRFUNC, g_strerror(errno)); + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_SOCKET; + return FALSE; + } + } + + session->read_buf_len = read_len; + } + + session_set_timeout(session, session->timeout_interval); + + terminator_len = strlen(session->read_data_terminator); + + if (session->read_buf_len == 0) + return TRUE; + + /* +---------------buf_data_len---------------+ + * +--preread_len--+-------read_buf_len-------+ + * +---------------+--------------------------+-------------------+ * + * ^data_begin_p ^read_buf_p + * ^read_buf + */ + + data_begin_p = session->read_buf_p - session->preread_len; + buf_data_len = session->preread_len + session->read_buf_len; + + /* check if data is terminated */ + if (buf_data_len >= terminator_len) { + if (session->read_data_pos == 0 && + buf_data_len == terminator_len && + memcmp(data_begin_p, session->read_data_terminator, + terminator_len) == 0) + complete = TRUE; + else if (buf_data_len >= terminator_len + 2 && + memcmp(data_begin_p + buf_data_len - + (terminator_len + 2), "\r\n", 2) == 0 && + memcmp(data_begin_p + buf_data_len - + terminator_len, session->read_data_terminator, + terminator_len) == 0) + complete = TRUE; + } + + /* incomplete read */ + if (!complete) { + GTimeVal tv_cur; + + if (buf_data_len <= PREREAD_SIZE) { + if (data_begin_p > session->read_buf) { + g_memmove(session->read_buf, data_begin_p, + buf_data_len); + data_begin_p = session->read_buf; + session->read_buf_p = session->read_buf + + session->preread_len; + } + session->read_buf_p += session->read_buf_len; + session->preread_len = buf_data_len; + session->read_buf_len = 0; + return TRUE; + } + + if (READ_BUF_LEFT() >= (SESSION_BUFFSIZE / 2)) { + session->read_buf_p += session->read_buf_len; + session->preread_len = buf_data_len; + session->read_buf_len = 0; + return TRUE; + } + + write_len = buf_data_len - PREREAD_SIZE; + if (fwrite(data_begin_p, write_len, 1, + session->read_data_fp) < 1) { + g_warning("session_read_data_as_file_cb: " + "writing data to file failed\n"); + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_IO; + return FALSE; + } + session->read_data_pos += write_len; + + g_memmove(session->read_buf, data_begin_p + write_len, + PREREAD_SIZE); + session->read_buf_p = session->read_buf + PREREAD_SIZE; + session->preread_len = PREREAD_SIZE; + session->read_buf_len = 0; + + g_get_current_time(&tv_cur); + if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 || + tv_cur.tv_usec - session->tv_prev.tv_usec > + UI_REFRESH_INTERVAL) { + if (session->recv_data_progressive_notify) + session->recv_data_progressive_notify + (session, session->read_data_pos, 0, + session->recv_data_progressive_notify_data); + g_get_current_time(&session->tv_prev); + } + + return TRUE; + } + + /* complete */ + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + write_len = buf_data_len - terminator_len; + if (write_len > 0 && fwrite(data_begin_p, write_len, 1, + session->read_data_fp) < 1) { + g_warning("session_read_data_as_file_cb: " + "writing data to file failed\n"); + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_IO; + return FALSE; + } + session->read_data_pos += write_len; + + if (fflush(session->read_data_fp) == EOF) { + perror("fflush"); + g_warning("session_read_data_as_file_cb: " + "writing data to file failed\n"); + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_IO; + return FALSE; + } + rewind(session->read_data_fp); + + session->preread_len = 0; + session->read_buf_len = 0; + session->read_buf_p = session->read_buf; + + /* callback */ + ret = session->recv_data_as_file_finished + (session, session->read_data_fp, session->read_data_pos); + + fclose(session->read_data_fp); + session->read_data_fp = NULL; + + if (session->recv_data_notify) + session->recv_data_notify(session, session->read_data_pos, + session->recv_data_notify_data); + + session->read_data_pos = 0; + + if (ret < 0) { + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_IO; + } + + return FALSE; +} + +static gint session_write_buf(Session *session) +{ + gint write_len; + gint to_write_len; + SessionPrivData *priv; + + g_return_val_if_fail(session->write_buf != NULL, -1); + g_return_val_if_fail(session->write_buf_p != NULL, -1); + g_return_val_if_fail(session->write_buf_len > 0, -1); + + to_write_len = session->write_buf_len - + (session->write_buf_p - session->write_buf); + to_write_len = MIN(to_write_len, SESSION_BUFFSIZE); + + write_len = sock_write(session->sock, session->write_buf_p, + to_write_len); + + if (write_len < 0) { + switch (errno) { + case EAGAIN: + write_len = 0; + break; + default: + g_warning("sock_write: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_SOCKET; + return -1; + } + } + + /* incomplete write */ + if (session->write_buf_p - session->write_buf + write_len < + session->write_buf_len) { + session->write_buf_p += write_len; + return 1; + } + + g_free(session->write_buf); + session->write_buf = NULL; + session->write_buf_p = NULL; + session->write_buf_len = 0; + + return 0; +} + +#define WRITE_DATA_BUFFSIZE 8192 + +static gint session_write_data(Session *session, gint *nwritten) +{ + gchar buf[WRITE_DATA_BUFFSIZE]; + gint write_len; + gint to_write_len; + SessionPrivData *priv; + + g_return_val_if_fail(session->write_data_fp != NULL, -1); + g_return_val_if_fail(session->write_data_pos >= 0, -1); + g_return_val_if_fail(session->write_data_len > 0, -1); + + to_write_len = session->write_data_len - session->write_data_pos; + to_write_len = MIN(to_write_len, WRITE_DATA_BUFFSIZE); + if (fread(buf, to_write_len, 1, session->write_data_fp) < 1) { + g_warning("session_write_data: reading data from file failed\n"); + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_IO; + return -1; + } + + write_len = sock_write(session->sock, buf, to_write_len); + + if (write_len < 0) { + switch (errno) { + case EAGAIN: + write_len = 0; + break; + default: + g_warning("sock_write: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_SOCKET; + *nwritten = write_len; + return -1; + } + } + + *nwritten = write_len; + + /* incomplete write */ + if (session->write_data_pos + write_len < session->write_data_len) { + session->write_data_pos += write_len; + if (write_len < to_write_len) { + if (fseek(session->write_data_fp, + session->write_data_pos, SEEK_SET) < 0) { + g_warning("session_write_data: file seek failed\n"); + session->state = SESSION_ERROR; + priv = session_get_priv(session); + priv->error_val = SESSION_ERROR_IO; + return -1; + } + } + return 1; + } + + session->write_data_fp = NULL; + session->write_data_pos = 0; + session->write_data_len = 0; + + return 0; +} + +static gboolean session_write_msg_cb(SockInfo *source, GIOCondition condition, + gpointer data) +{ + Session *session = SESSION(data); + SessionPrivData *priv; + gint ret; + + g_return_val_if_fail(condition == G_IO_OUT, FALSE); + g_return_val_if_fail(session->write_buf != NULL, FALSE); + g_return_val_if_fail(session->write_buf_p != NULL, FALSE); + g_return_val_if_fail(session->write_buf_len > 0, FALSE); + + ret = session_write_buf(session); + + if (ret < 0) { + session->state = SESSION_ERROR; + priv = session_get_priv(session); + if (priv->error_val == SESSION_ERROR_OK) + priv->error_val = SESSION_ERROR_IO; + return FALSE; + } else if (ret > 0) + return TRUE; + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + session_recv_msg(session); + + return FALSE; +} + +static gboolean session_write_data_cb(SockInfo *source, + GIOCondition condition, gpointer data) +{ + Session *session = SESSION(data); + SessionPrivData *priv; + guint write_data_len; + gint write_len; + gint ret; + + g_return_val_if_fail(condition == G_IO_OUT, FALSE); + g_return_val_if_fail(session->write_data_fp != NULL, FALSE); + g_return_val_if_fail(session->write_data_pos >= 0, FALSE); + g_return_val_if_fail(session->write_data_len > 0, FALSE); + + write_data_len = session->write_data_len; + + ret = session_write_data(session, &write_len); + + if (ret < 0) { + session->state = SESSION_ERROR; + priv = session_get_priv(session); + if (priv->error_val == SESSION_ERROR_OK) + priv->error_val = SESSION_ERROR_IO; + return FALSE; + } else if (ret > 0) { + GTimeVal tv_cur; + + g_get_current_time(&tv_cur); + if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 || + tv_cur.tv_usec - session->tv_prev.tv_usec > + UI_REFRESH_INTERVAL) { + session_set_timeout(session, session->timeout_interval); + if (session->send_data_progressive_notify) + session->send_data_progressive_notify + (session, + session->write_data_pos, + write_data_len, + session->send_data_progressive_notify_data); + g_get_current_time(&session->tv_prev); + } + return TRUE; + } + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + /* callback */ + ret = session->send_data_finished(session, write_data_len); + if (session->send_data_notify) + session->send_data_notify(session, write_data_len, + session->send_data_notify_data); + +#ifdef G_OS_WIN32 + sock_set_nonblocking_mode(session->sock, session->nonblocking); +#endif + + return FALSE; +} diff --git a/libsylph/session.h b/libsylph/session.h new file mode 100644 index 0000000..41c10e2 --- /dev/null +++ b/libsylph/session.h @@ -0,0 +1,248 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2012 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __SESSION_H__ +#define __SESSION_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +#include "socket.h" +#include "socks.h" +#include "utils.h" + +#define SESSION_BUFFSIZE 8192 + +typedef struct _Session Session; + +#define SESSION(obj) ((Session *)obj) + +typedef enum { + SESSION_UNKNOWN, + SESSION_IMAP, + SESSION_NEWS, + SESSION_SMTP, + SESSION_POP3 +} SessionType; + +typedef enum { + SESSION_READY, + SESSION_SEND, + SESSION_RECV, + SESSION_EOF, + SESSION_TIMEOUT, + SESSION_ERROR, + SESSION_DISCONNECTED +} SessionState; + +typedef enum +{ + SESSION_MSG_NORMAL, + SESSION_MSG_SEND_DATA, + SESSION_MSG_RECV_DATA, + SESSION_MSG_CONTROL, + SESSION_MSG_ERROR, + SESSION_MSG_UNKNOWN +} SessionMsgType; + +#ifndef USE_SSL +typedef enum +{ + SSL_NONE +} SSLType; +#endif + +typedef enum { + SESSION_ERROR_OK, + SESSION_ERROR_LOOKUP, + SESSION_ERROR_CONNFAIL, + SESSION_ERROR_IO, + SESSION_ERROR_SOCKET, + SESSION_ERROR_TIMEOUT, + SESSION_ERROR_ERROR +} SessionErrorValue; + +typedef gint (*RecvMsgNotify) (Session *session, + const gchar *msg, + gpointer user_data); +typedef gint (*RecvDataProgressiveNotify) (Session *session, + guint cur_len, + guint total_len, + gpointer user_data); +typedef gint (*RecvDataNotify) (Session *session, + guint len, + gpointer user_data); +typedef gint (*SendDataProgressiveNotify) (Session *session, + guint cur_len, + guint total_len, + gpointer user_data); +typedef gint (*SendDataNotify) (Session *session, + guint len, + gpointer user_data); + +struct _Session +{ + SessionType type; + + SockInfo *sock; + + gchar *server; + gushort port; + + SSLType ssl_type; + + gboolean nonblocking; + + SessionState state; + + stime_t last_access_time; + GTimeVal tv_prev; + + gint conn_id; + + gint io_tag; + + gchar read_buf[SESSION_BUFFSIZE]; + gchar *read_buf_p; + gint read_buf_len; + + /* buffer for short messages */ + GString *read_msg_buf; + + /* buffer for relatively short multiple lines data */ + GByteArray *read_data_buf; + gchar *read_data_terminator; + + /* buffer for large data */ + FILE *read_data_fp; + gint read_data_pos; + + gint preread_len; + + /* buffer for short messages */ + gchar *write_buf; + gchar *write_buf_p; + gint write_buf_len; + + /* buffer for large data */ + FILE *write_data_fp; + gint write_data_pos; + gint write_data_len; + + guint timeout_tag; + guint timeout_interval; + + guint idle_tag; + guint ping_tag; + + gpointer data; + + /* virtual methods to parse server responses */ + gint (*recv_msg) (Session *session, + const gchar *msg); + + gint (*send_data_finished) (Session *session, + guint len); + gint (*recv_data_finished) (Session *session, + guchar *data, + guint len); + + gint (*recv_data_as_file_finished) (Session *session, + FILE *fp, + guint len); + + void (*destroy) (Session *session); + + /* notification functions */ + RecvMsgNotify recv_msg_notify; + RecvDataProgressiveNotify recv_data_progressive_notify; + RecvDataNotify recv_data_notify; + SendDataProgressiveNotify send_data_progressive_notify; + SendDataNotify send_data_notify; + + gpointer recv_msg_notify_data; + gpointer recv_data_progressive_notify_data; + gpointer recv_data_notify_data; + gpointer send_data_progressive_notify_data; + gpointer send_data_notify_data; +}; + +void session_init (Session *session); +gint session_connect (Session *session, + const gchar *server, + gushort port); +gint session_connect_full (Session *session, + const gchar *server, + gushort port, + SocksInfo *socks_info); +gint session_disconnect (Session *session); +void session_destroy (Session *session); +gboolean session_is_connected (Session *session); + +SessionErrorValue session_get_error (Session *session); + +void session_set_access_time (Session *session); + +void session_set_timeout (Session *session, + guint interval); + +void session_set_recv_message_notify (Session *session, + RecvMsgNotify notify_func, + gpointer data); +void session_set_recv_data_progressive_notify + (Session *session, + RecvDataProgressiveNotify notify_func, + gpointer data); +void session_set_recv_data_notify (Session *session, + RecvDataNotify notify_func, + gpointer data); +void session_set_send_data_progressive_notify + (Session *session, + SendDataProgressiveNotify notify_func, + gpointer data); +void session_set_send_data_notify (Session *session, + SendDataNotify notify_func, + gpointer data); + +#if USE_SSL +gint session_start_tls (Session *session); +#endif + +gint session_send_msg (Session *session, + SessionMsgType type, + const gchar *msg); +gint session_recv_msg (Session *session); +gint session_send_data (Session *session, + FILE *data_fp, + guint size); +gint session_recv_data (Session *session, + guint size, + const gchar *terminator); + +gint session_recv_data_as_file (Session *session, + guint size, + const gchar *terminator); + +#endif /* __SESSION_H__ */ diff --git a/libsylph/smtp.c b/libsylph/smtp.c new file mode 100644 index 0000000..da41a97 --- /dev/null +++ b/libsylph/smtp.c @@ -0,0 +1,617 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2008 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +#include "smtp.h" +#include "md5_hmac.h" +#include "base64.h" +#include "utils.h" + +static void smtp_session_destroy(Session *session); + +static gint smtp_from(SMTPSession *session); + +static gint smtp_auth(SMTPSession *session); +static gint smtp_starttls(SMTPSession *session); +static gint smtp_auth_cram_md5(SMTPSession *session); +static gint smtp_auth_plain(SMTPSession *session); +static gint smtp_auth_login(SMTPSession *session); + +static gint smtp_ehlo(SMTPSession *session); +static gint smtp_ehlo_recv(SMTPSession *session, const gchar *msg); + +static gint smtp_helo(SMTPSession *session); +static gint smtp_rcpt(SMTPSession *session); +static gint smtp_data(SMTPSession *session); +static gint smtp_send_data(SMTPSession *session); +/* static gint smtp_rset(SMTPSession *session); */ +static gint smtp_quit(SMTPSession *session); +static gint smtp_eom(SMTPSession *session); + +static gint smtp_session_recv_msg(Session *session, const gchar *msg); +static gint smtp_session_send_data_finished(Session *session, guint len); + + +Session *smtp_session_new(void) +{ + SMTPSession *session; + + session = g_new0(SMTPSession, 1); + + session_init(SESSION(session)); + + SESSION(session)->type = SESSION_SMTP; + + SESSION(session)->recv_msg = smtp_session_recv_msg; + + SESSION(session)->recv_data_finished = NULL; + SESSION(session)->send_data_finished = smtp_session_send_data_finished; + + SESSION(session)->destroy = smtp_session_destroy; + + session->state = SMTP_READY; + +#if USE_SSL + session->tls_init_done = FALSE; +#endif + + session->hostname = NULL; + session->user = NULL; + session->pass = NULL; + + session->from = NULL; + session->to_list = NULL; + session->cur_to = NULL; + + session->send_data_fp = NULL; + session->send_data_len = 0; + + session->avail_auth_type = 0; + session->forced_auth_type = 0; + session->auth_type = 0; + + session->error_val = SM_OK; + session->error_msg = NULL; + + return SESSION(session); +} + +static void smtp_session_destroy(Session *session) +{ + SMTPSession *smtp_session = SMTP_SESSION(session); + + g_free(smtp_session->hostname); + g_free(smtp_session->user); + g_free(smtp_session->pass); + g_free(smtp_session->from); + + if (smtp_session->send_data_fp) + fclose(smtp_session->send_data_fp); + + g_free(smtp_session->error_msg); +} + +static gint smtp_from(SMTPSession *session) +{ + gchar buf[SMTPBUFSIZE]; + + g_return_val_if_fail(session->from != NULL, SM_ERROR); + + session->state = SMTP_FROM; + + if (strchr(session->from, '<')) + g_snprintf(buf, sizeof(buf), "MAIL FROM:%s", session->from); + else + g_snprintf(buf, sizeof(buf), "MAIL FROM:<%s>", session->from); + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf); + log_print("SMTP> %s\n", buf); + + return SM_OK; +} + +static gint smtp_auth(SMTPSession *session) +{ + + g_return_val_if_fail(session->user != NULL, SM_ERROR); + + session->state = SMTP_AUTH; + + if (session->forced_auth_type == SMTPAUTH_CRAM_MD5 || + (session->forced_auth_type == 0 && + (session->avail_auth_type & SMTPAUTH_CRAM_MD5) != 0)) + smtp_auth_cram_md5(session); + else if (session->forced_auth_type == SMTPAUTH_PLAIN || + (session->forced_auth_type == 0 && + (session->avail_auth_type & SMTPAUTH_PLAIN) != 0)) + smtp_auth_plain(session); + else if (session->forced_auth_type == SMTPAUTH_LOGIN || + (session->forced_auth_type == 0 && + (session->avail_auth_type & SMTPAUTH_LOGIN) != 0)) + smtp_auth_login(session); + else { + log_warning(_("SMTP AUTH not available\n")); + return SM_AUTHFAIL; + } + + return SM_OK; +} + +static gint smtp_auth_recv(SMTPSession *session, const gchar *msg) +{ + gchar buf[SMTPBUFSIZE]; + + switch (session->auth_type) { + case SMTPAUTH_LOGIN: + session->state = SMTP_AUTH_LOGIN_USER; + + if (!strncmp(msg, "334 ", 4)) { + base64_encode(buf, (guchar *)session->user, + strlen(session->user)); + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, + buf); + log_print("ESMTP> [USERID]\n"); + } else { + /* Server rejects AUTH */ + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, + "*"); + log_print("ESMTP> *\n"); + } + break; + case SMTPAUTH_CRAM_MD5: + session->state = SMTP_AUTH_CRAM_MD5; + + if (!strncmp(msg, "334 ", 4)) { + gchar *response; + gchar *response64; + guchar *challenge; + gint challengelen; + gchar hexdigest[33]; + + challenge = g_malloc(strlen(msg + 4) + 1); + challengelen = base64_decode(challenge, msg + 4, -1); + challenge[challengelen] = '\0'; + log_print("ESMTP< [Decoded: %s]\n", challenge); + + g_snprintf(buf, sizeof(buf), "%s", session->pass); + md5_hex_hmac(hexdigest, challenge, challengelen, + (guchar *)buf, strlen(buf)); + g_free(challenge); + + response = g_strdup_printf + ("%s %s", session->user, hexdigest); + log_print("ESMTP> [Encoded: %s]\n", response); + + response64 = g_malloc((strlen(response) + 3) * 2 + 1); + base64_encode(response64, (guchar *)response, + strlen(response)); + g_free(response); + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, + response64); + log_print("ESMTP> %s\n", response64); + g_free(response64); + } else { + /* Server rejects AUTH */ + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, + "*"); + log_print("ESMTP> *\n"); + } + break; + case SMTPAUTH_DIGEST_MD5: + default: + /* stop smtp_auth when no correct authtype */ + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "*"); + log_print("ESMTP> *\n"); + break; + } + + return SM_OK; +} + +static gint smtp_auth_login_user_recv(SMTPSession *session, const gchar *msg) +{ + gchar buf[SMTPBUFSIZE]; + + session->state = SMTP_AUTH_LOGIN_PASS; + + if (!strncmp(msg, "334 ", 4)) + base64_encode(buf, (guchar *)session->pass, + strlen(session->pass)); + else + /* Server rejects AUTH */ + g_snprintf(buf, sizeof(buf), "*"); + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf); + log_print("ESMTP> [PASSWORD]\n"); + + return SM_OK; +} + +static gint smtp_ehlo(SMTPSession *session) +{ + gchar buf[SMTPBUFSIZE]; + + session->state = SMTP_EHLO; + + session->avail_auth_type = 0; + + g_snprintf(buf, sizeof(buf), "EHLO %s", + session->hostname ? session->hostname : get_domain_name()); + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf); + log_print("ESMTP> %s\n", buf); + + return SM_OK; +} + +static gint smtp_ehlo_recv(SMTPSession *session, const gchar *msg) +{ + if (strncmp(msg, "250", 3) == 0) { + const gchar *p = msg; + p += 3; + if (*p == '-' || *p == ' ') p++; + if (g_ascii_strncasecmp(p, "AUTH", 4) == 0 && p[4] != '\0') { + p += 5; + if (strcasestr(p, "PLAIN")) + session->avail_auth_type |= SMTPAUTH_PLAIN; + if (strcasestr(p, "LOGIN")) + session->avail_auth_type |= SMTPAUTH_LOGIN; + if (strcasestr(p, "CRAM-MD5")) + session->avail_auth_type |= SMTPAUTH_CRAM_MD5; + if (strcasestr(p, "DIGEST-MD5")) + session->avail_auth_type |= SMTPAUTH_DIGEST_MD5; + } + return SM_OK; + } else if ((msg[0] == '1' || msg[0] == '2' || msg[0] == '3') && + (msg[3] == ' ' || msg[3] == '\0')) + return SM_OK; + else if (msg[0] == '5' && msg[1] == '0' && + (msg[2] == '4' || msg[2] == '3' || msg[2] == '1')) + return SM_ERROR; + + return SM_ERROR; +} + +static gint smtp_starttls(SMTPSession *session) +{ + session->state = SMTP_STARTTLS; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "STARTTLS"); + log_print("ESMTP> STARTTLS\n"); + + return SM_OK; +} + +static gint smtp_auth_cram_md5(SMTPSession *session) +{ + session->state = SMTP_AUTH; + session->auth_type = SMTPAUTH_CRAM_MD5; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "AUTH CRAM-MD5"); + log_print("ESMTP> AUTH CRAM-MD5\n"); + + return SM_OK; +} + +static gint smtp_auth_plain(SMTPSession *session) +{ + gchar *authstr; + gint authlen; + gchar *outbuf; + gchar *p; + + session->state = SMTP_AUTH_PLAIN; + session->auth_type = SMTPAUTH_PLAIN; + + /* + * construct the string: \0\0 + */ + + authlen = 1 + strlen(session->user) + 1 + strlen(session->pass); + authstr = g_malloc(authlen + 1); + + p = authstr; + + *p++ = '\0'; + strcpy(p, session->user); + p += strlen(p) + 1; + strcpy(p, session->pass); + + outbuf = g_malloc(sizeof("AUTH PLAIN ") + authlen * 2 + 1); + + strcpy(outbuf, "AUTH PLAIN "); + p = outbuf + strlen(outbuf); + base64_encode(p, (guchar *)authstr, authlen); + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, outbuf); + log_print("ESMTP> AUTH PLAIN ********\n"); + + g_free(outbuf); + g_free(authstr); + + return SM_OK; +} + +static gint smtp_auth_login(SMTPSession *session) +{ + session->state = SMTP_AUTH; + session->auth_type = SMTPAUTH_LOGIN; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "AUTH LOGIN"); + log_print("ESMTP> AUTH LOGIN\n"); + + return SM_OK; +} + +static gint smtp_helo(SMTPSession *session) +{ + gchar buf[SMTPBUFSIZE]; + + session->state = SMTP_HELO; + + g_snprintf(buf, sizeof(buf), "HELO %s", + session->hostname ? session->hostname : get_domain_name()); + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf); + log_print("SMTP> %s\n", buf); + + return SM_OK; +} + +static gint smtp_rcpt(SMTPSession *session) +{ + gchar buf[SMTPBUFSIZE]; + gchar *to; + + g_return_val_if_fail(session->cur_to != NULL, SM_ERROR); + + session->state = SMTP_RCPT; + + to = (gchar *)session->cur_to->data; + + if (strchr(to, '<')) + g_snprintf(buf, sizeof(buf), "RCPT TO:%s", to); + else + g_snprintf(buf, sizeof(buf), "RCPT TO:<%s>", to); + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf); + log_print("SMTP> %s\n", buf); + + session->cur_to = session->cur_to->next; + + return SM_OK; +} + +static gint smtp_data(SMTPSession *session) +{ + session->state = SMTP_DATA; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "DATA"); + log_print("SMTP> DATA\n"); + + return SM_OK; +} + +static gint smtp_send_data(SMTPSession *session) +{ + session->state = SMTP_SEND_DATA; + + session_send_data(SESSION(session), session->send_data_fp, + session->send_data_len); + + return SM_OK; +} + +#if 0 +static gint smtp_rset(SMTPSession *session) +{ + session->state = SMTP_RSET; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "RSET"); + log_print("SMTP> RSET\n"); + + return SM_OK; +} +#endif + +static gint smtp_quit(SMTPSession *session) +{ + session->state = SMTP_QUIT; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "QUIT"); + log_print("SMTP> QUIT\n"); + + return SM_OK; +} + +static gint smtp_eom(SMTPSession *session) +{ + session->state = SMTP_EOM; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "."); + log_print("SMTP> . (EOM)\n"); + + return SM_OK; +} + +static gint smtp_session_recv_msg(Session *session, const gchar *msg) +{ + SMTPSession *smtp_session = SMTP_SESSION(session); + gboolean cont = FALSE; + + if (strlen(msg) < 4) { + log_warning(_("bad SMTP response\n")); + return -1; + } + + switch (smtp_session->state) { + case SMTP_EHLO: + case SMTP_STARTTLS: + case SMTP_AUTH: + case SMTP_AUTH_PLAIN: + case SMTP_AUTH_LOGIN_USER: + case SMTP_AUTH_LOGIN_PASS: + case SMTP_AUTH_CRAM_MD5: + log_print("ESMTP< %s\n", msg); + break; + default: + log_print("SMTP< %s\n", msg); + break; + } + + if (msg[0] == '5' && msg[1] == '0' && + (msg[2] == '4' || msg[2] == '3' || msg[2] == '1')) { + log_warning(_("error occurred on SMTP session\n")); + smtp_session->state = SMTP_ERROR; + smtp_session->error_val = SM_ERROR; + g_free(smtp_session->error_msg); + smtp_session->error_msg = g_strdup(msg); + return -1; + } + + if (!strncmp(msg, "535", 3)) { + log_warning(_("error occurred on authentication\n")); + smtp_session->state = SMTP_ERROR; + smtp_session->error_val = SM_AUTHFAIL; + g_free(smtp_session->error_msg); + smtp_session->error_msg = g_strdup(msg); + return -1; + } + + if (msg[0] != '1' && msg[0] != '2' && msg[0] != '3') { + log_warning(_("error occurred on SMTP session\n")); + smtp_session->state = SMTP_ERROR; + smtp_session->error_val = SM_ERROR; + g_free(smtp_session->error_msg); + smtp_session->error_msg = g_strdup(msg); + return -1; + } + + if (msg[3] == '-') + cont = TRUE; + else if (msg[3] != ' ' && msg[3] != '\0') { + log_warning(_("bad SMTP response\n")); + smtp_session->state = SMTP_ERROR; + smtp_session->error_val = SM_UNRECOVERABLE; + return -1; + } + + /* ignore all multiline responses except for EHLO */ + if (cont && smtp_session->state != SMTP_EHLO) + return session_recv_msg(session); + + switch (smtp_session->state) { + case SMTP_READY: + case SMTP_CONNECTED: +#if USE_SSL + if (smtp_session->user || session->ssl_type != SSL_NONE) +#else + if (smtp_session->user) +#endif + smtp_ehlo(smtp_session); + else + smtp_helo(smtp_session); + break; + case SMTP_HELO: + smtp_from(smtp_session); + break; + case SMTP_EHLO: + smtp_ehlo_recv(smtp_session, msg); + if (cont == TRUE) + break; +#if USE_SSL + if (session->ssl_type == SSL_STARTTLS && + smtp_session->tls_init_done == FALSE) { + smtp_starttls(smtp_session); + break; + } +#endif + if (smtp_session->user) { + if (smtp_auth(smtp_session) != SM_OK) + smtp_from(smtp_session); + } else + smtp_from(smtp_session); + break; + case SMTP_STARTTLS: +#if USE_SSL + if (session_start_tls(session) < 0) { + log_warning(_("can't start TLS session\n")); + smtp_session->state = SMTP_ERROR; + smtp_session->error_val = SM_ERROR; + return -1; + } + smtp_session->tls_init_done = TRUE; + smtp_ehlo(smtp_session); +#endif + break; + case SMTP_AUTH: + smtp_auth_recv(smtp_session, msg); + break; + case SMTP_AUTH_LOGIN_USER: + smtp_auth_login_user_recv(smtp_session, msg); + break; + case SMTP_AUTH_PLAIN: + case SMTP_AUTH_LOGIN_PASS: + case SMTP_AUTH_CRAM_MD5: + smtp_from(smtp_session); + break; + case SMTP_FROM: + if (smtp_session->cur_to) + smtp_rcpt(smtp_session); + break; + case SMTP_RCPT: + if (smtp_session->cur_to) + smtp_rcpt(smtp_session); + else + smtp_data(smtp_session); + break; + case SMTP_DATA: + smtp_send_data(smtp_session); + break; + case SMTP_EOM: + smtp_quit(smtp_session); + break; + case SMTP_QUIT: + session_disconnect(session); + break; + case SMTP_ERROR: + default: + log_warning(_("error occurred on SMTP session\n")); + smtp_session->error_val = SM_ERROR; + return -1; + } + + if (cont) + return session_recv_msg(session); + + return 0; +} + +static gint smtp_session_send_data_finished(Session *session, guint len) +{ + smtp_eom(SMTP_SESSION(session)); + return 0; +} diff --git a/libsylph/smtp.h b/libsylph/smtp.h new file mode 100644 index 0000000..6bf3ce5 --- /dev/null +++ b/libsylph/smtp.h @@ -0,0 +1,116 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2008 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __SMTP_H__ +#define __SMTP_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "session.h" + +typedef struct _SMTPSession SMTPSession; + +#define SMTP_SESSION(obj) ((SMTPSession *)obj) + +#define SMTPBUFSIZE 8192 + +typedef enum +{ + SM_OK = 0, + SM_ERROR = 128, + SM_UNRECOVERABLE = 129, + SM_AUTHFAIL = 130 +} SMTPErrorValue; + +typedef enum +{ + ESMTP_8BITMIME = 1 << 0, + ESMTP_SIZE = 1 << 1, + ESMTP_ETRN = 1 << 2 +} ESMTPFlag; + +typedef enum +{ + SMTPAUTH_LOGIN = 1 << 0, + SMTPAUTH_CRAM_MD5 = 1 << 1, + SMTPAUTH_DIGEST_MD5 = 1 << 2, + SMTPAUTH_PLAIN = 1 << 3 +} SMTPAuthType; + +typedef enum +{ + SMTP_READY, + SMTP_CONNECTED, + SMTP_HELO, + SMTP_EHLO, + SMTP_STARTTLS, + SMTP_FROM, + SMTP_AUTH, + SMTP_AUTH_PLAIN, + SMTP_AUTH_LOGIN_USER, + SMTP_AUTH_LOGIN_PASS, + SMTP_AUTH_CRAM_MD5, + SMTP_RCPT, + SMTP_DATA, + SMTP_SEND_DATA, + SMTP_EOM, + SMTP_RSET, + SMTP_QUIT, + SMTP_ERROR, + SMTP_DISCONNECTED, + + N_SMTP_PHASE +} SMTPState; + +struct _SMTPSession +{ + Session session; + + SMTPState state; + + gboolean tls_init_done; + + gchar *hostname; + + gchar *user; + gchar *pass; + + gchar *from; + GSList *to_list; + GSList *cur_to; + + FILE *send_data_fp; + gint send_data_len; + + SMTPAuthType avail_auth_type; + SMTPAuthType forced_auth_type; + SMTPAuthType auth_type; + + SMTPErrorValue error_val; + gchar *error_msg; +}; + +Session *smtp_session_new (void); + +#endif /* __SMTP_H__ */ diff --git a/libsylph/socket.c b/libsylph/socket.c new file mode 100644 index 0000000..228c8db --- /dev/null +++ b/libsylph/socket.c @@ -0,0 +1,2179 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2012 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#ifdef G_OS_WIN32 +# include +# include +#else +# if HAVE_SYS_WAIT_H +# include +# endif +# include +# include +# include +# include +# include +# include +# include +#endif /* G_OS_WIN32 */ +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_SYS_SELECT_H +# include +#endif + +#include "socket.h" +#if USE_SSL +# include "ssl.h" +#endif + +#include "utils.h" + +#define BUFFSIZE 8192 + +#ifdef G_OS_WIN32 +#define SockDesc SOCKET +#define SOCKET_IS_VALID(s) ((s) != INVALID_SOCKET) +#else +#define SockDesc gint +#define SOCKET_IS_VALID(s) ((s) >= 0) +#define INVALID_SOCKET (-1) +#endif + +typedef gint (*SockAddrFunc) (GList *addr_list, + gpointer data); + +typedef struct _SockConnectData SockConnectData; +typedef struct _SockLookupData SockLookupData; +typedef struct _SockAddrData SockAddrData; +typedef struct _SockSource SockSource; + +struct _SockConnectData { + gint id; + gchar *hostname; + gushort port; +#ifdef G_OS_UNIX + GList *addr_list; + GList *cur_addr; + SockLookupData *lookup_data; + GIOChannel *channel; + guint io_tag; +#endif /* G_OS_UNIX */ +#if USE_THREADS + gint flag; + GThread *thread; +#endif /* G_OS_UNIX */ + SockInfo *sock; + SockConnectFunc func; + gpointer data; +}; + +struct _SockLookupData { + gchar *hostname; + pid_t child_pid; + GIOChannel *channel; + guint io_tag; + SockAddrFunc func; + gpointer data; +}; + +struct _SockAddrData { + gint family; + gint socktype; + gint protocol; + gint addr_len; + struct sockaddr *addr; +}; + +struct _SockSource { + GSource parent; + SockInfo *sock; +}; + +static guint io_timeout = 60; + +static GList *sock_connect_data_list = NULL; +static GList *sock_list = NULL; + +static gboolean sock_prepare (GSource *source, + gint *timeout); +static gboolean sock_check (GSource *source); +static gboolean sock_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); + +GSourceFuncs sock_watch_funcs = { + sock_prepare, + sock_check, + sock_dispatch, + NULL +}; + +static SockInfo *sock_find_from_fd (gint fd); + +static gint sock_connect_with_timeout (gint sock, + const struct sockaddr *serv_addr, + gint addrlen, + guint timeout_secs); + +#ifndef INET6 +static gint sock_info_connect_by_hostname + (SockInfo *sock); +#else +#ifdef G_OS_WIN32 +typedef int (*GetAddrInfoFunc) (const char *node, + const char *service, + const struct addrinfo *hints, + struct addrinfo **res); +typedef void (*FreeAddrInfoFunc) (struct addrinfo *res); + +static GetAddrInfoFunc getaddrinfo_func = NULL; +static FreeAddrInfoFunc freeaddrinfo_func = NULL; + +#undef getaddrinfo +#define getaddrinfo my_getaddrinfo +#undef freeaddrinfo +#define freeaddrinfo my_freeaddrinfo +#endif + +static SockDesc sock_info_connect_by_getaddrinfo(SockInfo *sock); +#endif + +#ifdef G_OS_UNIX +static void sock_address_list_free (GList *addr_list); + +static gboolean sock_connect_async_cb (GIOChannel *source, + GIOCondition condition, + gpointer data); +static gint sock_connect_async_get_address_info_cb + (GList *addr_list, + gpointer data); + +static gint sock_connect_address_list_async (SockConnectData *conn_data); + +static gboolean sock_get_address_info_async_cb (GIOChannel *source, + GIOCondition condition, + gpointer data); +static SockLookupData *sock_get_address_info_async + (const gchar *hostname, + gushort port, + SockAddrFunc func, + gpointer data); +static gint sock_get_address_info_async_cancel (SockLookupData *lookup_data); +#endif /* G_OS_UNIX */ + + +gint sock_init(void) +{ +#ifdef G_OS_WIN32 + WSADATA wsadata; + gint result; + + result = WSAStartup(MAKEWORD(2, 2), &wsadata); + if (result != NO_ERROR) { + g_warning("WSAStartup() failed\n"); + return -1; + } +#endif + return 0; +} + +gint sock_cleanup(void) +{ +#ifdef G_OS_WIN32 + WSACleanup(); +#endif + return 0; +} + +gint sock_set_io_timeout(guint sec) +{ + io_timeout = sec; + return 0; +} + +gint fd_connect_inet(gushort port) +{ + SockDesc sock; + struct sockaddr_in addr; + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (!SOCKET_IS_VALID(sock)) { +#ifdef G_OS_WIN32 + g_warning("fd_connect_inet(): socket() failed: %ld\n", + WSAGetLastError()); +#else + perror("fd_connect_inet(): socket"); +#endif + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + fd_close(sock); + return -1; + } + + return sock; +} + +gint fd_open_inet(gushort port) +{ + SockDesc sock; + struct sockaddr_in addr; + gint val; + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (!SOCKET_IS_VALID(sock)) { +#ifdef G_OS_WIN32 + g_warning("fd_open_inet(): socket() failed: %ld\n", + WSAGetLastError()); +#else + perror("fd_open_inet(): socket"); +#endif + return -1; + } + + val = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&val, + sizeof(val)) < 0) { + perror("setsockopt"); + fd_close(sock); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("bind"); + fd_close(sock); + return -1; + } + + if (listen(sock, 1) < 0) { + perror("listen"); + fd_close(sock); + return -1; + } + + return sock; +} + +gint fd_connect_unix(const gchar *path) +{ +#ifdef G_OS_UNIX + gint sock; + struct sockaddr_un addr; + + sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + perror("fd_connect_unix(): socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + fd_close(sock); + return -1; + } + + return sock; +#else + return -1; +#endif +} + +gint fd_open_unix(const gchar *path) +{ +#ifdef G_OS_UNIX + gint sock; + struct sockaddr_un addr; + gint val; + + sock = socket(PF_UNIX, SOCK_STREAM, 0); + + if (sock < 0) { + perror("sock_open_unix(): socket"); + return -1; + } + + val = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { + perror("setsockopt"); + fd_close(sock); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("bind"); + fd_close(sock); + return -1; + } + + if (listen(sock, 1) < 0) { + perror("listen"); + fd_close(sock); + return -1; + } + + return sock; +#else + return -1; +#endif +} + +gint fd_accept(gint sock) +{ + struct sockaddr_in caddr; + guint caddr_len; + + caddr_len = sizeof(caddr); + return accept(sock, (struct sockaddr *)&caddr, &caddr_len); +} + + +SockInfo *sock_new(const gchar *hostname, gushort port) +{ + SockInfo *sockinfo; + + sockinfo = g_new0(SockInfo, 1); + sockinfo->sock = INVALID_SOCKET; + sockinfo->sock_ch = NULL; + sockinfo->hostname = g_strdup(hostname); + sockinfo->port = port; + sockinfo->state = CONN_READY; + sockinfo->flags = 0; + sockinfo->data = NULL; + + return sockinfo; +} + +static SockInfo *sock_find_from_fd(gint fd) +{ + GList *cur; + + for (cur = sock_list; cur != NULL; cur = cur->next) { + if (((SockInfo *)cur->data)->sock == fd) + return (SockInfo *)cur->data; + } + + return NULL; +} + +static gint set_nonblocking_mode(gint fd, gboolean nonblock) +{ +#ifdef G_OS_WIN32 + gulong val = nonblock ? 1 : 0; + SockInfo *sock; + + if (!nonblock) + WSAEventSelect(fd, NULL, 0); + if (ioctlsocket(fd, FIONBIO, &val) == SOCKET_ERROR) { + g_warning("set_nonblocking_mode(): ioctlsocket() failed: %ld\n", + WSAGetLastError()); + return -1; + } + + sock = sock_find_from_fd(fd); + if (sock) { + if (nonblock) { + SOCK_SET_FLAGS(sock->flags, SYL_SOCK_NONBLOCK); + } else { + SOCK_UNSET_FLAGS(sock->flags, SYL_SOCK_NONBLOCK); + } + } + debug_print("set nonblocking mode to %d\n", nonblock); + + return 0; +#else + gint flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + perror("fcntl"); + return -1; + } + + if (nonblock) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + return fcntl(fd, F_SETFL, flags); +#endif +} + +gint sock_set_nonblocking_mode(SockInfo *sock, gboolean nonblock) +{ + gint ret; + + g_return_val_if_fail(sock != NULL, -1); + + ret = set_nonblocking_mode(sock->sock, nonblock); + if (ret == 0) { + if (nonblock) { + SOCK_SET_FLAGS(sock->flags, SYL_SOCK_NONBLOCK); + } else { + SOCK_UNSET_FLAGS(sock->flags, SYL_SOCK_NONBLOCK); + } + } + + return ret; +} + +static gboolean is_nonblocking_mode(gint fd) +{ +#ifdef G_OS_WIN32 + SockInfo *sock; + + sock = sock_find_from_fd(fd); + if (sock) { + return SOCK_IS_NONBLOCK(sock->flags); + } + + return FALSE; +#else + gint flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + perror("fcntl"); + return FALSE; + } + + return ((flags & O_NONBLOCK) != 0); +#endif +} + +gboolean sock_is_nonblocking_mode(SockInfo *sock) +{ + g_return_val_if_fail(sock != NULL, FALSE); + +#ifdef G_OS_WIN32 + return SOCK_IS_NONBLOCK(sock->flags); +#else + return is_nonblocking_mode(sock->sock); +#endif +} + +gboolean sock_has_read_data(SockInfo *sock) +{ +#ifdef G_OS_WIN32 + gulong val; + +#if USE_SSL + if (sock->ssl) + return TRUE; +#endif + if (ioctlsocket(sock->sock, FIONREAD, &val) < 0) { + g_warning("sock_has_read_data(): ioctlsocket() failed: %ld\n", + WSAGetLastError()); + return TRUE; + } + + if (val == 0) + return FALSE; + else + return TRUE; +#else + return TRUE; +#endif +} + + +static gboolean sock_prepare(GSource *source, gint *timeout) +{ + *timeout = 1; + return FALSE; +} + +static gboolean sock_check(GSource *source) +{ + SockInfo *sock = ((SockSource *)source)->sock; + struct timeval timeout = {0, 0}; + fd_set fds; + GIOCondition condition = sock->condition; + +#if USE_SSL + if (sock->ssl) { + if (condition & G_IO_IN) { + if (SSL_pending(sock->ssl) > 0) + return TRUE; + if (SSL_want_write(sock->ssl)) + condition |= G_IO_OUT; + } + + if (condition & G_IO_OUT) { + if (SSL_want_read(sock->ssl)) + condition |= G_IO_IN; + } + } +#endif + + FD_ZERO(&fds); + FD_SET(sock->sock, &fds); + + select(sock->sock + 1, + (condition & G_IO_IN) ? &fds : NULL, + (condition & G_IO_OUT) ? &fds : NULL, + NULL, &timeout); + + return FD_ISSET(sock->sock, &fds) != 0; +} + +static gboolean sock_dispatch(GSource *source, GSourceFunc callback, + gpointer user_data) +{ + SockInfo *sock = ((SockSource *)source)->sock; + + return sock->callback(sock, sock->condition, sock->data); +} + +static gboolean sock_watch_cb(GIOChannel *source, GIOCondition condition, + gpointer data) +{ + SockInfo *sock = (SockInfo *)data; + + if ((condition & sock->condition) == 0) + return TRUE; + + return sock->callback(sock, sock->condition, sock->data); +} + +guint sock_add_watch(SockInfo *sock, GIOCondition condition, SockFunc func, + gpointer data) +{ + sock->callback = func; + sock->condition = condition; + sock->data = data; + +#if USE_SSL + if (sock->ssl) { + GSource *source; + + source = g_source_new(&sock_watch_funcs, sizeof(SockSource)); + ((SockSource *)source)->sock = sock; + g_source_set_priority(source, G_PRIORITY_DEFAULT); + g_source_set_can_recurse(source, FALSE); + return g_source_attach(source, NULL); + } +#endif + + return g_io_add_watch(sock->sock_ch, condition, sock_watch_cb, sock); +} + +guint sock_add_watch_poll(SockInfo *sock, GIOCondition condition, SockFunc func, + gpointer data) +{ + GSource *source; + + sock->callback = func; + sock->condition = condition; + sock->data = data; + + source = g_source_new(&sock_watch_funcs, sizeof(SockSource)); + ((SockSource *)source)->sock = sock; + g_source_set_priority(source, G_PRIORITY_DEFAULT); + g_source_set_can_recurse(source, FALSE); + + return g_source_attach(source, NULL); +} + +static gint fd_check_io(gint fd, GIOCondition cond) +{ + struct timeval timeout; + fd_set fds; + SockInfo *sock; + + sock = sock_find_from_fd(fd); + if (sock && !SOCK_IS_CHECK_IO(sock->flags)) + return 0; + + timeout.tv_sec = io_timeout; + timeout.tv_usec = 0; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + if (cond == G_IO_IN) { + select(fd + 1, &fds, NULL, NULL, + io_timeout > 0 ? &timeout : NULL); + } else { + select(fd + 1, NULL, &fds, NULL, + io_timeout > 0 ? &timeout : NULL); + } + + if (FD_ISSET(fd, &fds)) { + return 0; + } else { + g_warning("Socket IO timeout\n"); + return -1; + } +} + +#if defined(G_OS_UNIX) && !defined(USE_THREADS) +static sigjmp_buf jmpenv; + +static void timeout_handler(gint sig) +{ + siglongjmp(jmpenv, 1); +} +#endif + +static gint sock_connect_with_timeout(gint sock, + const struct sockaddr *serv_addr, + gint addrlen, + guint timeout_secs) +{ + gint ret; + +#ifdef G_OS_WIN32 + WSAEVENT hevent; + gint err; + DWORD dwret; + WSANETWORKEVENTS events; + + errno = 0; + + hevent = WSACreateEvent(); + if (hevent == WSA_INVALID_EVENT) + return -1; + + ret = WSAEventSelect(sock, hevent, FD_CONNECT); + if (ret == SOCKET_ERROR) { + g_warning("sock_connect_with_timeout: WSAEventSelect"); + WSACloseEvent(hevent); + return -1; + } + + ret = connect(sock, serv_addr, addrlen); + + if (ret == SOCKET_ERROR) { + err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) { + g_warning("sock_connect_with_timeout: connect (%d)", err); + ret = -1; + goto end; + } + } + + dwret = WSAWaitForMultipleEvents(1, &hevent, FALSE, timeout_secs * 1000, FALSE); + if (dwret == WSA_WAIT_TIMEOUT) { + g_warning("sock_connect_with_timeout: timeout"); + errno = WSAETIMEDOUT; + ret = -1; + goto end; + } else if (dwret != WSA_WAIT_EVENT_0) { + g_warning("sock_connect_with_timeout: WSAWaitForMultipleEvents (%d)", dwret); + ret = -1; + goto end; + } + + ret = WSAEnumNetworkEvents(sock, hevent, &events); + if (ret == SOCKET_ERROR) { + g_warning("sock_connect_with_timeout: WSAEnumNetworkEvents (%d)", ret); + ret = -1; + goto end; + } + + if ((events.lNetworkEvents & FD_CONNECT) && + events.iErrorCode[FD_CONNECT_BIT] == 0) { + ret = 0; + errno = 0; + } else + ret = -1; + +end: + WSAEventSelect(sock, NULL, 0); + WSACloseEvent(hevent); + + set_nonblocking_mode(sock, FALSE); +#else + set_nonblocking_mode(sock, TRUE); + + ret = connect(sock, serv_addr, addrlen); + + if (ret < 0) { + if (EINPROGRESS == errno) { + fd_set fds; + struct timeval tv; + + tv.tv_sec = timeout_secs; + tv.tv_usec = 0; + FD_ZERO(&fds); + FD_SET(sock, &fds); + do { + ret = select(sock + 1, NULL, &fds, NULL, &tv); + } while (ret < 0 && EINTR == errno); + if (ret < 0) { + perror("sock_connect_with_timeout: select"); + return -1; + } else if (ret == 0) { + debug_print("sock_connect_with_timeout: timeout\n"); + errno = ETIMEDOUT; + return -1; + } else { + gint val; + guint len; + + if (FD_ISSET(sock, &fds)) { + ret = 0; + } else { + debug_print("sock_connect_with_timeout: fd not set\n"); + return -1; + } + + len = sizeof(val); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &val, &len) < 0) { + perror("sock_connect_with_timeout: getsockopt"); + return -1; + } + if (val != 0) { + debug_print("sock_connect_with_timeout: getsockopt(SOL_SOCKET, SO_ERROR) returned error: %s\n", g_strerror(val)); + return -1; + } + } + } else { + perror("sock_connect_with_timeout: connect"); + return -1; + } + } + + set_nonblocking_mode(sock, FALSE); +#endif + + return ret; +} + +static void resolver_init(void) +{ +#ifdef G_OS_UNIX + static time_t resolv_conf_mtime = 0; + GStatBuf s; + + if (g_stat("/etc/resolv.conf", &s) == 0 && + s.st_mtime != resolv_conf_mtime) { + debug_print("Reloading /etc/resolv.conf\n"); + resolv_conf_mtime = s.st_mtime; + res_init(); + } +#endif +} + +struct hostent *my_gethostbyname(const gchar *hostname) +{ + struct hostent *hp; +#if defined(G_OS_UNIX) && !defined(USE_THREADS) + void (*prev_handler)(gint); + + alarm(0); + prev_handler = signal(SIGALRM, timeout_handler); + if (sigsetjmp(jmpenv, 1)) { + alarm(0); + signal(SIGALRM, prev_handler); + fprintf(stderr, "%s: host lookup timed out.\n", hostname); + errno = 0; + return NULL; + } + alarm(io_timeout); +#endif + + if ((hp = gethostbyname(hostname)) == NULL) { +#if defined(G_OS_UNIX) && !defined(USE_THREADS) + alarm(0); + signal(SIGALRM, prev_handler); +#endif + fprintf(stderr, "%s: unknown host.\n", hostname); + errno = 0; + return NULL; + } + +#if defined(G_OS_UNIX) && !defined(USE_THREADS) + alarm(0); + signal(SIGALRM, prev_handler); +#endif + + return hp; +} + +static void sock_set_buffer_size(gint sock) +{ +#ifdef G_OS_WIN32 + gint val; + guint len = sizeof(val); + +#define SOCK_BUFFSIZE 32768 + + getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&val, &len); + if (val < SOCK_BUFFSIZE) { + val = SOCK_BUFFSIZE; + setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&val, len); + } + getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&val, &len); + if (val < SOCK_BUFFSIZE) { + val = SOCK_BUFFSIZE; + setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&val, len); + } + getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&val, &len); + debug_print("SO_SNDBUF = %d\n", val); + getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&val, &len); + debug_print("SO_RCVBUF = %d\n", val); + +#undef SOCK_BUFFSIZE +#endif +} + +#if !defined(INET6) || defined(G_OS_WIN32) +static gint my_inet_aton(const gchar *hostname, struct in_addr *inp) +{ +#if HAVE_INET_ATON + return inet_aton(hostname, inp); +#else +#if HAVE_INET_ADDR + guint32 inaddr; + + inaddr = inet_addr(hostname); + if (inaddr != -1) { + memcpy(inp, &inaddr, sizeof(inaddr)); + return 1; + } else + return 0; +#else + return 0; +#endif +#endif /* HAVE_INET_ATON */ +} +#endif /* !defined(INET6) || defined(G_OS_WIN32) */ + +#ifndef INET6 +static gint sock_info_connect_by_hostname(SockInfo *sock) +{ + struct hostent *hp; + struct sockaddr_in ad; + gint ret; + + g_return_val_if_fail(sock != NULL, -1); + g_return_val_if_fail(sock->hostname != NULL && sock->port > 0, -1); + + resolver_init(); + + memset(&ad, 0, sizeof(ad)); + ad.sin_family = AF_INET; + ad.sin_port = htons(sock->port); + + if (!my_inet_aton(sock->hostname, &ad.sin_addr)) { + if ((hp = my_gethostbyname(sock->hostname)) == NULL) { + fprintf(stderr, "%s: unknown host.\n", sock->hostname); + errno = 0; + sock->state = CONN_LOOKUPFAILED; + return -1; + } + + if (hp->h_length != 4 && hp->h_length != 8) { + fprintf(stderr, "illegal address length received for host %s\n", sock->hostname); + errno = 0; + sock->state = CONN_LOOKUPFAILED; + return -1; + } + + memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); + } + + sock->state = CONN_LOOKUPSUCCESS; + + ret = sock_connect_with_timeout(sock->sock, (struct sockaddr *)&ad, + sizeof(ad), io_timeout); + if (ret < 0) + sock->state = CONN_FAILED; + else + sock->state = CONN_ESTABLISHED; + + return ret; +} + +#else /* INET6 */ + +#ifdef G_OS_WIN32 +static gboolean win32_ipv6_supported(void) +{ + static gboolean ipv6_checked = FALSE; + HMODULE hmodule; + + if (ipv6_checked) + return getaddrinfo_func != NULL; + + hmodule = GetModuleHandleA("ws2_32"); + if (hmodule) { + getaddrinfo_func = + (GetAddrInfoFunc)GetProcAddress(hmodule, "getaddrinfo"); + freeaddrinfo_func = + (FreeAddrInfoFunc)GetProcAddress(hmodule, "freeaddrinfo"); + if (!getaddrinfo_func || !freeaddrinfo_func) { + getaddrinfo_func = NULL; + freeaddrinfo_func = NULL; + } + } + + if (getaddrinfo_func) + debug_print("ws2_32 has IPv6 functions.\n"); + else + debug_print("ws2_32 does not have IPv6 functions.\n"); + + ipv6_checked = TRUE; + return getaddrinfo_func != NULL; +} + +/* subset of getaddrinfo() */ +static int my_getaddrinfo(const char *node, const char *service, + const struct addrinfo *hintp, + struct addrinfo **res) +{ + struct addrinfo *ai; + struct sockaddr_in addr, *paddr; + struct addrinfo hints; + gint port = 0; + + if (win32_ipv6_supported()) + return getaddrinfo_func(node, service, hintp, res); + + if (!hintp) { + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + } else + memcpy(&hints, hintp, sizeof(hints)); + + if (hints.ai_family != AF_UNSPEC && hints.ai_family != AF_INET) + return EAI_FAMILY; + if (hints.ai_socktype == 0) + hints.ai_socktype = SOCK_STREAM; + if (hints.ai_protocol == 0) + hints.ai_protocol = IPPROTO_TCP; + if (hints.ai_socktype != SOCK_STREAM) + return EAI_SOCKTYPE; + if (hints.ai_protocol != IPPROTO_TCP) + return EAI_SOCKTYPE; +#if 0 + if (!node && !service) + return EAI_NONAME; +#endif + if (!node || !service) + return EAI_NONAME; + + port = atoi(service); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + if (!my_inet_aton(node, &addr.sin_addr)) { + struct hostent *hp; + + if ((hp = my_gethostbyname(node)) == NULL) { + fprintf(stderr, "%s: unknown host.\n", node); + errno = 0; + return EAI_NONAME; + } + if (hp->h_length != 4 && hp->h_length != 8) { + fprintf(stderr, "illegal address length received for host %s\n", node); + errno = 0; + return EAI_FAIL; + } + + memcpy(&addr.sin_addr, hp->h_addr, hp->h_length); + } + + ai = g_malloc0(sizeof(struct addrinfo)); + paddr = g_malloc(sizeof(struct sockaddr_in)); + memcpy(paddr, &addr, sizeof(struct sockaddr_in)); + + ai->ai_flags = 0; + ai->ai_family = AF_INET; + ai->ai_socktype = hints.ai_socktype; + ai->ai_protocol = hints.ai_protocol; + ai->ai_addrlen = sizeof(struct sockaddr_in); + ai->ai_addr = (struct sockaddr *)paddr; + ai->ai_canonname = NULL; + ai->ai_next = NULL; + + *res = ai; + + return 0; +} + +static void my_freeaddrinfo(struct addrinfo *res) +{ + if (win32_ipv6_supported()) { + freeaddrinfo_func(res); + return; + } + + if (res) { + g_free(res->ai_addr); + g_free(res); + } +} +#endif + +static SockDesc sock_info_connect_by_getaddrinfo(SockInfo *sockinfo) +{ + SockDesc sock = INVALID_SOCKET; + gint gai_error; + struct addrinfo hints, *res, *ai; + gchar port_str[6]; + + g_return_val_if_fail(sockinfo != NULL, INVALID_SOCKET); + g_return_val_if_fail(sockinfo->hostname != NULL && sockinfo->port > 0, INVALID_SOCKET); + + resolver_init(); + + memset(&hints, 0, sizeof(hints)); + /* hints.ai_flags = AI_CANONNAME; */ + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + /* convert port from integer to string. */ + g_snprintf(port_str, sizeof(port_str), "%d", sockinfo->port); + + if ((gai_error = getaddrinfo(sockinfo->hostname, port_str, &hints, &res)) != 0) { +#ifdef G_OS_WIN32 + fprintf(stderr, "getaddrinfo for %s:%s failed: errno: %d\n", + sockinfo->hostname, port_str, gai_error); +#else + fprintf(stderr, "getaddrinfo for %s:%s failed: %s\n", + sockinfo->hostname, port_str, gai_strerror(gai_error)); +#endif + debug_print("getaddrinfo failed\n"); + sockinfo->state = CONN_LOOKUPFAILED; + return INVALID_SOCKET; + } + + for (ai = res; ai != NULL; ai = ai->ai_next) { + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (!SOCKET_IS_VALID(sock)) + continue; + sock_set_buffer_size(sock); + + if (sock_connect_with_timeout + (sock, ai->ai_addr, ai->ai_addrlen, io_timeout) == 0) + break; + + fd_close(sock); + } + + if (res != NULL) + freeaddrinfo(res); + + if (ai == NULL) { + sockinfo->state = CONN_FAILED; + return INVALID_SOCKET; + } + + sockinfo->state = CONN_ESTABLISHED; + return sock; +} +#endif /* !INET6 */ + +SockInfo *sock_connect(const gchar *hostname, gushort port) +{ + SockInfo *sockinfo; + + sockinfo = sock_new(hostname, port); + if (sock_info_connect(sockinfo) < 0) { + sock_close(sockinfo); + return NULL; + } + + return sockinfo; +} + +gint sock_info_connect(SockInfo *sockinfo) +{ + SockDesc sock; +#ifndef INET6 + gint ret; +#endif + + g_return_val_if_fail(sockinfo != NULL, -1); + g_return_val_if_fail(sockinfo->hostname != NULL && sockinfo->port > 0, + -1); + +#ifdef INET6 + sock = sock_info_connect_by_getaddrinfo(sockinfo); + if (!SOCKET_IS_VALID(sock)) { + return -1; + } +#else + sock = socket(AF_INET, SOCK_STREAM, 0); + if (!SOCKET_IS_VALID(sock)) { +#ifdef G_OS_WIN32 + g_warning("socket() failed: %ld\n", WSAGetLastError()); +#else + perror("socket"); +#endif /* G_OS_WIN32 */ + sockinfo->state = CONN_FAILED; + return -1; + } + sock_set_buffer_size(sock); + + sockinfo->sock = sock; + if ((ret = sock_info_connect_by_hostname(sockinfo)) < 0) { + if (errno != 0) perror("connect"); + fd_close(sock); + sockinfo->sock = INVALID_SOCKET; + return ret; + } +#endif /* INET6 */ + + sockinfo->sock = sock; + sockinfo->sock_ch = g_io_channel_unix_new(sock); + sockinfo->flags = SYL_SOCK_CHECK_IO; + + sock_list = g_list_prepend(sock_list, sockinfo); + + g_usleep(100000); + + return 0; +} + +#ifdef G_OS_UNIX +static void sock_address_list_free(GList *addr_list) +{ + GList *cur; + + for (cur = addr_list; cur != NULL; cur = cur->next) { + SockAddrData *addr_data = (SockAddrData *)cur->data; + g_free(addr_data->addr); + g_free(addr_data); + } + + g_list_free(addr_list); +} + +/* asynchronous TCP connection */ + +static gboolean sock_connect_async_cb(GIOChannel *source, + GIOCondition condition, gpointer data) +{ + SockConnectData *conn_data = (SockConnectData *)data; + gint fd; + gint val; + guint len; + SockInfo *sockinfo; + + fd = g_io_channel_unix_get_fd(source); + + conn_data->io_tag = 0; + conn_data->channel = NULL; + g_io_channel_unref(source); + + if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { + debug_print("sock_connect_async_cb: condition = %d\n", + condition); + fd_close(fd); + sock_connect_address_list_async(conn_data); + return FALSE; + } + + len = sizeof(val); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &val, &len) < 0) { + perror("getsockopt"); + fd_close(fd); + sock_connect_address_list_async(conn_data); + return FALSE; + } + + if (val != 0) { + debug_print("getsockopt(SOL_SOCKET, SO_ERROR) returned error\n"); + fd_close(fd); + sock_connect_address_list_async(conn_data); + return FALSE; + } + + sockinfo = conn_data->sock; + sockinfo->sock = fd; + sockinfo->sock_ch = g_io_channel_unix_new(fd); + sockinfo->state = CONN_ESTABLISHED; + sockinfo->flags = SYL_SOCK_NONBLOCK; + + sock_list = g_list_prepend(sock_list, sockinfo); + + conn_data->func(sockinfo, conn_data->data); + + conn_data->sock = NULL; + sock_connect_async_cancel(conn_data->id); + + return FALSE; +} + +static gint sock_connect_async_get_address_info_cb(GList *addr_list, + gpointer data) +{ + SockConnectData *conn_data = (SockConnectData *)data; + + conn_data->addr_list = addr_list; + conn_data->cur_addr = addr_list; + conn_data->lookup_data = NULL; + + return sock_connect_address_list_async(conn_data); +} + +gint sock_connect_async(const gchar *hostname, gushort port, + SockConnectFunc func, gpointer data) +{ + SockInfo *sock; + gint ret; + + sock = sock_new(hostname, port); + ret = sock_info_connect_async(sock, func, data); + if (ret < 0) + sock_close(sock); + + return ret; +} + +gint sock_info_connect_async(SockInfo *sock, SockConnectFunc func, + gpointer data) +{ + static gint id = 1; + SockConnectData *conn_data; + + g_return_val_if_fail(sock != NULL, -1); + g_return_val_if_fail(sock->hostname != NULL && sock->port > 0, -1); + + conn_data = g_new0(SockConnectData, 1); + conn_data->id = id++; + conn_data->hostname = g_strdup(sock->hostname); + conn_data->port = sock->port; + conn_data->addr_list = NULL; + conn_data->cur_addr = NULL; + conn_data->io_tag = 0; + conn_data->sock = sock; + conn_data->func = func; + conn_data->data = data; + + conn_data->lookup_data = sock_get_address_info_async + (sock->hostname, sock->port, + sock_connect_async_get_address_info_cb, conn_data); + + if (conn_data->lookup_data == NULL) { + g_free(conn_data->hostname); + g_free(conn_data); + return -1; + } + + sock_connect_data_list = g_list_append(sock_connect_data_list, + conn_data); + + return conn_data->id; +} + +gint sock_connect_async_cancel(gint id) +{ + SockConnectData *conn_data = NULL; + GList *cur; + + for (cur = sock_connect_data_list; cur != NULL; cur = cur->next) { + if (((SockConnectData *)cur->data)->id == id) { + conn_data = (SockConnectData *)cur->data; + break; + } + } + + if (conn_data) { + sock_connect_data_list = g_list_remove(sock_connect_data_list, + conn_data); + + if (conn_data->lookup_data) + sock_get_address_info_async_cancel + (conn_data->lookup_data); + + if (conn_data->io_tag > 0) + g_source_remove(conn_data->io_tag); + if (conn_data->channel) { + g_io_channel_shutdown(conn_data->channel, FALSE, NULL); + g_io_channel_unref(conn_data->channel); + } + if (conn_data->sock) + sock_close(conn_data->sock); + + sock_address_list_free(conn_data->addr_list); + g_free(conn_data->hostname); + g_free(conn_data); + } else { + g_warning("sock_connect_async_cancel: id %d not found.\n", id); + return -1; + } + + return 0; +} + +static gint sock_connect_address_list_async(SockConnectData *conn_data) +{ + SockAddrData *addr_data; + gint sock = -1; + + if (conn_data->addr_list == NULL) { + g_warning("sock_connect_address_list_async: " + "DNS lookup for %s failed", conn_data->hostname); + conn_data->sock->state = CONN_LOOKUPFAILED; + conn_data->func(conn_data->sock, conn_data->data); + sock_connect_async_cancel(conn_data->id); + return -1; + } + + for (; conn_data->cur_addr != NULL; + conn_data->cur_addr = conn_data->cur_addr->next) { + addr_data = (SockAddrData *)conn_data->cur_addr->data; + + if ((sock = socket(addr_data->family, addr_data->socktype, + addr_data->protocol)) < 0) { + perror("socket"); + continue; + } + + sock_set_buffer_size(sock); + set_nonblocking_mode(sock, TRUE); + + if (connect(sock, addr_data->addr, addr_data->addr_len) < 0) { + if (EINPROGRESS == errno) { + break; + } else { + perror("connect"); + fd_close(sock); + } + } else + break; + } + + if (conn_data->cur_addr == NULL) { + g_warning("sock_connect_address_list_async: " + "connection to %s:%d failed", + conn_data->hostname, conn_data->port); + conn_data->sock->state = CONN_FAILED; + conn_data->func(conn_data->sock, conn_data->data); + sock_connect_async_cancel(conn_data->id); + return -1; + } + + debug_print("sock_connect_address_list_async: waiting for connect\n"); + + conn_data->cur_addr = conn_data->cur_addr->next; + + conn_data->channel = g_io_channel_unix_new(sock); + conn_data->io_tag = g_io_add_watch + (conn_data->channel, G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + sock_connect_async_cb, conn_data); + + return 0; +} + +static gint sock_kill_process(pid_t pid) +{ + pid_t ret = (pid_t)-1; + + kill(pid, SIGKILL); + + while (ret == (pid_t)-1) { + if ((ret = waitpid(pid, NULL, 0)) != pid) { + if (ret == (pid_t)-1 && errno != EINTR) { + perror("sock_kill_process(): waitpid"); + break; + } + } + } + + return (gint)pid; +} + +/* asynchronous DNS lookup */ + +static gboolean sock_get_address_info_async_cb(GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + SockLookupData *lookup_data = (SockLookupData *)data; + GList *addr_list = NULL; + SockAddrData *addr_data; + gsize bytes_read; + gint ai_member[4]; + struct sockaddr *addr; + + for (;;) { + if (g_io_channel_read(source, (gchar *)ai_member, + sizeof(ai_member), &bytes_read) + != G_IO_ERROR_NONE) { + g_warning("sock_get_address_info_async_cb: " + "address length read error\n"); + break; + } + + if (bytes_read == 0 || bytes_read != sizeof(ai_member)) + break; + + if (ai_member[0] == AF_UNSPEC) { + g_warning("DNS lookup failed"); + break; + } + + addr = g_malloc(ai_member[3]); + if (g_io_channel_read(source, (gchar *)addr, ai_member[3], + &bytes_read) + != G_IO_ERROR_NONE) { + g_warning("sock_get_address_info_async_cb: " + "address data read error\n"); + g_free(addr); + break; + } + + if (bytes_read != ai_member[3]) { + g_warning("sock_get_address_info_async_cb: " + "incomplete address data\n"); + g_free(addr); + break; + } + + addr_data = g_new0(SockAddrData, 1); + addr_data->family = ai_member[0]; + addr_data->socktype = ai_member[1]; + addr_data->protocol = ai_member[2]; + addr_data->addr_len = ai_member[3]; + addr_data->addr = addr; + + addr_list = g_list_append(addr_list, addr_data); + } + + g_io_channel_shutdown(source, FALSE, NULL); + g_io_channel_unref(source); + + sock_kill_process(lookup_data->child_pid); + + lookup_data->func(addr_list, lookup_data->data); + + g_free(lookup_data->hostname); + g_free(lookup_data); + + return FALSE; +} + +static SockLookupData *sock_get_address_info_async(const gchar *hostname, + gushort port, + SockAddrFunc func, + gpointer data) +{ + SockLookupData *lookup_data = NULL; + gint pipe_fds[2]; + pid_t pid; + + resolver_init(); + + if (pipe(pipe_fds) < 0) { + perror("pipe"); + func(NULL, data); + return NULL; + } + + if ((pid = fork()) < 0) { + perror("fork"); + func(NULL, data); + return NULL; + } + + /* child process */ + if (pid == 0) { +#ifdef INET6 + gint gai_err; + struct addrinfo hints, *res, *ai; + gchar port_str[6]; +#else /* !INET6 */ + struct hostent *hp; + gchar **addr_list_p; + struct sockaddr_in ad; +#endif /* INET6 */ + gint ai_member[4] = {AF_UNSPEC, 0, 0, 0}; + + close(pipe_fds[0]); + +#ifdef INET6 + memset(&hints, 0, sizeof(hints)); + /* hints.ai_flags = AI_CANONNAME; */ + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + g_snprintf(port_str, sizeof(port_str), "%d", port); + + gai_err = getaddrinfo(hostname, port_str, &hints, &res); + if (gai_err != 0) { + g_warning("getaddrinfo for %s:%s failed: %s", + hostname, port_str, gai_strerror(gai_err)); + fd_write_all(pipe_fds[1], (gchar *)ai_member, + sizeof(ai_member)); + close(pipe_fds[1]); + _exit(1); + } + + for (ai = res; ai != NULL; ai = ai->ai_next) { + ai_member[0] = ai->ai_family; + ai_member[1] = ai->ai_socktype; + ai_member[2] = ai->ai_protocol; + ai_member[3] = ai->ai_addrlen; + + fd_write_all(pipe_fds[1], (gchar *)ai_member, + sizeof(ai_member)); + fd_write_all(pipe_fds[1], (gchar *)ai->ai_addr, + ai->ai_addrlen); + } + + if (res != NULL) + freeaddrinfo(res); +#else /* !INET6 */ + hp = my_gethostbyname(hostname); + if (hp == NULL || hp->h_addrtype != AF_INET) { + fd_write_all(pipe_fds[1], (gchar *)ai_member, + sizeof(ai_member)); + close(pipe_fds[1]); + _exit(1); + } + + ai_member[0] = AF_INET; + ai_member[1] = SOCK_STREAM; + ai_member[2] = IPPROTO_TCP; + ai_member[3] = sizeof(ad); + + memset(&ad, 0, sizeof(ad)); + ad.sin_family = AF_INET; + ad.sin_port = htons(port); + + for (addr_list_p = hp->h_addr_list; *addr_list_p != NULL; + addr_list_p++) { + memcpy(&ad.sin_addr, *addr_list_p, hp->h_length); + fd_write_all(pipe_fds[1], (gchar *)ai_member, + sizeof(ai_member)); + fd_write_all(pipe_fds[1], (gchar *)&ad, sizeof(ad)); + } +#endif /* INET6 */ + + close(pipe_fds[1]); + + _exit(0); + } else { + close(pipe_fds[1]); + + lookup_data = g_new0(SockLookupData, 1); + lookup_data->hostname = g_strdup(hostname); + lookup_data->child_pid = pid; + lookup_data->func = func; + lookup_data->data = data; + + lookup_data->channel = g_io_channel_unix_new(pipe_fds[0]); + lookup_data->io_tag = g_io_add_watch + (lookup_data->channel, G_IO_IN, + sock_get_address_info_async_cb, lookup_data); + } + + return lookup_data; +} + +static gint sock_get_address_info_async_cancel(SockLookupData *lookup_data) +{ + if (lookup_data->io_tag > 0) + g_source_remove(lookup_data->io_tag); + if (lookup_data->channel) { + g_io_channel_shutdown(lookup_data->channel, FALSE, NULL); + g_io_channel_unref(lookup_data->channel); + } + + if (lookup_data->child_pid > 0) + sock_kill_process(lookup_data->child_pid); + + g_free(lookup_data->hostname); + g_free(lookup_data); + + return 0; +} +#endif /* G_OS_UNIX */ + +#if USE_THREADS +static gpointer sock_connect_async_func(gpointer data) +{ + SockConnectData *conn_data = (SockConnectData *)data; + gint ret; + + ret = sock_info_connect(conn_data->sock); + + if (ret == 0) { + debug_print("sock_connect_async_func: connected\n"); + } else { + if (conn_data->sock->state == CONN_LOOKUPFAILED) + debug_print("sock_connect_async_func: DNS lookup failed\n"); + else + debug_print("sock_connect_async_func: connection failed\n"); + } + + g_atomic_int_set(&conn_data->flag, 1); + g_main_context_wakeup(NULL); + + debug_print("sock_connect_async_func: exit\n"); + return GINT_TO_POINTER(ret); +} + +gint sock_connect_async_thread(const gchar *hostname, gushort port) +{ + SockInfo *sock; + gint ret; + + sock = sock_new(hostname, port); + ret = sock_info_connect_async_thread(sock); + if (ret < 0) + sock_close(sock); + + return ret; +} + +gint sock_info_connect_async_thread(SockInfo *sock) +{ + static gint id = 1; + SockConnectData *data; + + g_return_val_if_fail(sock != NULL, -1); + g_return_val_if_fail(sock->hostname != NULL && sock->port > 0, -1); + + data = g_new0(SockConnectData, 1); + data->id = id++; + data->hostname = g_strdup(sock->hostname); + data->port = sock->port; + data->flag = 0; + data->sock = sock; + + data->thread = g_thread_create(sock_connect_async_func, data, TRUE, + NULL); + if (!data->thread) { + g_free(data->hostname); + g_free(data); + return -1; + } + + sock_connect_data_list = g_list_append(sock_connect_data_list, data); + + return data->id; +} + +gint sock_connect_async_thread_wait(gint id, SockInfo **sock) +{ + gint ret; + + *sock = NULL; + ret = sock_info_connect_async_thread_wait(id, sock); + if (ret < 0) { + if (*sock) { + sock_close(*sock); + *sock = NULL; + } + } + + return ret; +} + +gint sock_info_connect_async_thread_wait(gint id, SockInfo **sock) +{ + SockConnectData *conn_data = NULL; + GList *cur; + gint ret; + + for (cur = sock_connect_data_list; cur != NULL; cur = cur->next) { + if (((SockConnectData *)cur->data)->id == id) { + conn_data = (SockConnectData *)cur->data; + break; + } + } + + if (!conn_data) { + g_warning("sock_info_connect_async_thread_wait: id %d not found.", id); + return -1; + } + + debug_print("sock_connect_async_thread_wait: waiting thread\n"); + while (g_atomic_int_get(&conn_data->flag) == 0) + event_loop_iterate(); + + ret = GPOINTER_TO_INT(g_thread_join(conn_data->thread)); + debug_print("sock_info_connect_async_thread_wait: thread exited with status %d\n", ret); + + if (sock) + *sock = conn_data->sock; + + sock_connect_data_list = g_list_remove(sock_connect_data_list, + conn_data); + g_free(conn_data->hostname); + g_free(conn_data); + + return ret; +} +#endif /* USE_THREADS */ + +gint sock_printf(SockInfo *sock, const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE]; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + return sock_write_all(sock, buf, strlen(buf)); +} + +#ifdef G_OS_WIN32 +static void sock_set_errno_from_last_error(gint error) +{ + switch (error) { + case WSAEWOULDBLOCK: + errno = EAGAIN; + break; + default: + debug_print("last error = %d\n", error); + errno = 0; + break; + } +} +#endif + +gint sock_read(SockInfo *sock, gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_read(sock->ssl, buf, len); +#endif + return fd_read(sock->sock, buf, len); +} + +gint fd_read(gint fd, gchar *buf, gint len) +{ +#ifdef G_OS_WIN32 + return fd_recv(fd, buf, len, 0); +#else + if (fd_check_io(fd, G_IO_IN) < 0) + return -1; + + return read(fd, buf, len); +#endif +} + +#if USE_SSL +gint ssl_read(SSL *ssl, gchar *buf, gint len) +{ + gint err, ret; + + errno = 0; + + if (SSL_pending(ssl) == 0) { + if (fd_check_io(SSL_get_rfd(ssl), G_IO_IN) < 0) + return -1; + } + + ret = SSL_read(ssl, buf, len); + + switch ((err = SSL_get_error(ssl, ret))) { + case SSL_ERROR_NONE: + return ret; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + errno = EAGAIN; + return -1; + case SSL_ERROR_ZERO_RETURN: + return 0; + default: + g_warning("SSL_read() returned error %d, ret = %d\n", err, ret); + if (ret == 0) + return 0; +#ifdef G_OS_WIN32 + errno = EIO; +#endif + return -1; + } +} +#endif + +gint sock_write(SockInfo *sock, const gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_write(sock->ssl, buf, len); +#endif + return fd_write(sock->sock, buf, len); +} + +gint fd_write(gint fd, const gchar *buf, gint len) +{ +#ifdef G_OS_WIN32 + gint ret; +#endif + if (fd_check_io(fd, G_IO_OUT) < 0) + return -1; + +#ifdef G_OS_WIN32 + ret = send(fd, buf, len, 0); + if (ret == SOCKET_ERROR) { + gint err; + err = WSAGetLastError(); + sock_set_errno_from_last_error(err); + if (err != WSAEWOULDBLOCK) + g_warning("fd_write() failed with %d (errno = %d)\n", + err, errno); + } + return ret; +#else + return write(fd, buf, len); +#endif +} + +#if USE_SSL +gint ssl_write(SSL *ssl, const gchar *buf, gint len) +{ + gint ret; + + ret = SSL_write(ssl, buf, len); + + switch (SSL_get_error(ssl, ret)) { + case SSL_ERROR_NONE: + return ret; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + errno = EAGAIN; + return -1; + default: + return -1; + } +} +#endif + +gint sock_write_all(SockInfo *sock, const gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_write_all(sock->ssl, buf, len); +#endif + return fd_write_all(sock->sock, buf, len); +} + +gint fd_write_all(gint fd, const gchar *buf, gint len) +{ + gint n, wrlen = 0; + + while (len) { + n = fd_write(fd, buf, len); + if (n <= 0) + return -1; + len -= n; + wrlen += n; + buf += n; + } + + return wrlen; +} + +#if USE_SSL +gint ssl_write_all(SSL *ssl, const gchar *buf, gint len) +{ + gint n, wrlen = 0; + + while (len) { + n = ssl_write(ssl, buf, len); + if (n <= 0) + return -1; + len -= n; + wrlen += n; + buf += n; + } + + return wrlen; +} +#endif + +gint fd_recv(gint fd, gchar *buf, gint len, gint flags) +{ +#ifdef G_OS_WIN32 + gint ret; +#endif + if (fd_check_io(fd, G_IO_IN) < 0) + return -1; + +#ifdef G_OS_WIN32 + ret = recv(fd, buf, len, flags); + if (ret == SOCKET_ERROR) { + gint err; + err = WSAGetLastError(); + sock_set_errno_from_last_error(err); + if (err != WSAEWOULDBLOCK) + g_warning("fd_recv(): failed with %d (errno = %d)\n", + err, errno); + } + return ret; +#else + return recv(fd, buf, len, flags); +#endif +} + +gint fd_gets(gint fd, gchar *buf, gint len) +{ + gchar *newline, *bp = buf; + gint n; + + if (--len < 1) + return -1; + do { + if ((n = fd_recv(fd, bp, len, MSG_PEEK)) <= 0) + return -1; + if ((newline = memchr(bp, '\n', n)) != NULL) + n = newline - bp + 1; + if ((n = fd_read(fd, bp, n)) < 0) + return -1; + bp += n; + len -= n; + } while (!newline && len); + + *bp = '\0'; + return bp - buf; +} + +#if USE_SSL +gint ssl_gets(SSL *ssl, gchar *buf, gint len) +{ + gchar *newline, *bp = buf; + gint n; + + if (--len < 1) + return -1; + do { + if ((n = ssl_peek(ssl, bp, len)) <= 0) + return -1; + if ((newline = memchr(bp, '\n', n)) != NULL) + n = newline - bp + 1; + if ((n = ssl_read(ssl, bp, n)) < 0) + return -1; + bp += n; + len -= n; + } while (!newline && len); + + *bp = '\0'; + return bp - buf; +} +#endif + +gint sock_gets(SockInfo *sock, gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_gets(sock->ssl, buf, len); +#endif + return fd_gets(sock->sock, buf, len); +} + +gint fd_getline(gint fd, gchar **line) +{ + gchar buf[BUFFSIZE]; + gchar *str = NULL; + gint len; + gulong size = 0; + gulong cur_offset = 0; + + while ((len = fd_gets(fd, buf, sizeof(buf))) > 0) { + size += len; + str = g_realloc(str, size + 1); + memcpy(str + cur_offset, buf, len + 1); + cur_offset += len; + if (buf[len - 1] == '\n') + break; + } + + *line = str; + + if (!str) + return -1; + else + return (gint)size; +} + +#if USE_SSL +gint ssl_getline(SSL *ssl, gchar **line) +{ + gchar buf[BUFFSIZE]; + gchar *str = NULL; + gint len; + gulong size = 0; + gulong cur_offset = 0; + + while ((len = ssl_gets(ssl, buf, sizeof(buf))) > 0) { + size += len; + str = g_realloc(str, size + 1); + memcpy(str + cur_offset, buf, len + 1); + cur_offset += len; + if (buf[len - 1] == '\n') + break; + } + + *line = str; + + if (!str) + return -1; + else + return (gint)size; +} +#endif + +gint sock_getline(SockInfo *sock, gchar **line) +{ + g_return_val_if_fail(sock != NULL, -1); + g_return_val_if_fail(line != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_getline(sock->ssl, line); +#endif + return fd_getline(sock->sock, line); +} + +gint sock_puts(SockInfo *sock, const gchar *buf) +{ + gint ret; + + if ((ret = sock_write_all(sock, buf, strlen(buf))) < 0) + return ret; + return sock_write_all(sock, "\r\n", 2); +} + +/* peek at the socket data without actually reading it */ +#if USE_SSL +gint ssl_peek(SSL *ssl, gchar *buf, gint len) +{ + gint err, ret; + + if (SSL_pending(ssl) == 0) { + if (fd_check_io(SSL_get_rfd(ssl), G_IO_IN) < 0) + return -1; + } + + ret = SSL_peek(ssl, buf, len); + + switch ((err = SSL_get_error(ssl, ret))) { + case SSL_ERROR_NONE: + return ret; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + errno = EAGAIN; + return -1; + case SSL_ERROR_ZERO_RETURN: + return 0; + default: + g_warning("SSL_peek() returned error %d, ret = %d\n", err, ret); + if (ret == 0) + return 0; + return -1; + } +} +#endif + +gint sock_peek(SockInfo *sock, gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_peek(sock->ssl, buf, len); +#endif + return fd_recv(sock->sock, buf, len, MSG_PEEK); +} + +gint sock_close(SockInfo *sock) +{ + GList *cur; + + if (!sock) + return 0; + + debug_print("sock_close: %s:%u (%p)\n", sock->hostname ? sock->hostname : "(none)", sock->port, sock); + +#if USE_SSL + if (sock->ssl) + ssl_done_socket(sock); +#endif + + if (sock->sock_ch) { + g_io_channel_shutdown(sock->sock_ch, FALSE, NULL); + g_io_channel_unref(sock->sock_ch); + } + + for (cur = sock_list; cur != NULL; cur = cur->next) { + if ((SockInfo *)cur->data == sock) { + sock_list = g_list_remove(sock_list, sock); + break; + } + } + + g_free(sock->hostname); + g_free(sock); + + return 0; +} + +gint fd_close(gint fd) +{ +#ifdef G_OS_WIN32 + return closesocket(fd); +#else + return close(fd); +#endif +} diff --git a/libsylph/socket.h b/libsylph/socket.h new file mode 100644 index 0000000..15bcef7 --- /dev/null +++ b/libsylph/socket.h @@ -0,0 +1,163 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2012 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __SOCKET_H__ +#define __SOCKET_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#if HAVE_NETDB_H +# include +#endif +#ifdef G_OS_WIN32 +# include +#endif + +typedef struct _SockInfo SockInfo; + +#if USE_SSL +# include "ssl.h" +#endif + +typedef enum +{ + CONN_READY, + CONN_LOOKUPSUCCESS, + CONN_ESTABLISHED, + CONN_LOOKUPFAILED, + CONN_FAILED +} ConnectionState; + +typedef enum +{ + SYL_SOCK_NONBLOCK = 1 << 0, + SYL_SOCK_CHECK_IO = 1 << 1 +} SockFlags; + +#define SOCK_SET_FLAGS(flags, set) { (flags) |= (set); } +#define SOCK_UNSET_FLAGS(flags, set) { (flags) &= ~(set); } +#define SOCK_IS_NONBLOCK(flags) ((flags & SYL_SOCK_NONBLOCK) != 0) +#define SOCK_IS_CHECK_IO(flags) ((flags & SYL_SOCK_CHECK_IO) != 0) + +typedef gint (*SockConnectFunc) (SockInfo *sock, + gpointer data); +typedef gboolean (*SockFunc) (SockInfo *sock, + GIOCondition condition, + gpointer data); + +struct _SockInfo +{ + gint sock; +#if USE_SSL + SSL *ssl; +#else + gpointer ssl; +#endif + GIOChannel *sock_ch; + + gchar *hostname; + gushort port; + ConnectionState state; + SockFlags flags; + gpointer data; + + SockFunc callback; + GIOCondition condition; +}; + +gint sock_init (void); +gint sock_cleanup (void); + +gint sock_set_io_timeout (guint sec); + +SockInfo *sock_new (const gchar *hostname, gushort port); + +gint sock_set_nonblocking_mode (SockInfo *sock, gboolean nonblock); +gboolean sock_is_nonblocking_mode (SockInfo *sock); + +gboolean sock_has_read_data (SockInfo *sock); + +guint sock_add_watch (SockInfo *sock, GIOCondition condition, + SockFunc func, gpointer data); +guint sock_add_watch_poll (SockInfo *sock, GIOCondition condition, + SockFunc func, gpointer data); + +struct hostent *my_gethostbyname (const gchar *hostname); + +SockInfo *sock_connect (const gchar *hostname, gushort port); +#ifdef G_OS_UNIX +gint sock_connect_async (const gchar *hostname, gushort port, + SockConnectFunc func, gpointer data); +gint sock_connect_async_cancel (gint id); +#endif +#if USE_THREADS +gint sock_connect_async_thread (const gchar *hostname, gushort port); +gint sock_connect_async_thread_wait (gint id, SockInfo **sock); +#endif + +gint sock_info_connect (SockInfo *sock); +#ifdef G_OS_UNIX +gint sock_info_connect_async (SockInfo *sock, + SockConnectFunc func, gpointer data); +#endif +#if USE_THREADS +gint sock_info_connect_async_thread (SockInfo *sock); +gint sock_info_connect_async_thread_wait(gint id, SockInfo **sock); +#endif + +/* Basic I/O functions */ +gint sock_printf (SockInfo *sock, const gchar *format, ...) + G_GNUC_PRINTF(2, 3); +gint sock_read (SockInfo *sock, gchar *buf, gint len); +gint sock_write (SockInfo *sock, const gchar *buf, gint len); +gint sock_write_all (SockInfo *sock, const gchar *buf, gint len); +gint sock_gets (SockInfo *sock, gchar *buf, gint len); +gint sock_getline (SockInfo *sock, gchar **line); +gint sock_puts (SockInfo *sock, const gchar *buf); +gint sock_peek (SockInfo *sock, gchar *buf, gint len); +gint sock_close (SockInfo *sock); + +/* Functions to directly work on FD. They are needed for pipes */ +gint fd_connect_inet (gushort port); +gint fd_open_inet (gushort port); +gint fd_connect_unix (const gchar *path); +gint fd_open_unix (const gchar *path); +gint fd_accept (gint sock); + +gint fd_read (gint sock, gchar *buf, gint len); +gint fd_write (gint sock, const gchar *buf, gint len); +gint fd_write_all (gint sock, const gchar *buf, gint len); +gint fd_gets (gint sock, gchar *buf, gint len); +gint fd_getline (gint sock, gchar **line); +gint fd_close (gint sock); + +/* Functions for SSL */ +#if USE_SSL +gint ssl_read (SSL *ssl, gchar *buf, gint len); +gint ssl_write (SSL *ssl, const gchar *buf, gint len); +gint ssl_write_all (SSL *ssl, const gchar *buf, gint len); +gint ssl_gets (SSL *ssl, gchar *buf, gint len); +gint ssl_getline (SSL *ssl, gchar **line); +gint ssl_peek (SSL *ssl, gchar *buf, gint len); +#endif + +#endif /* __SOCKET_H__ */ diff --git a/libsylph/socks.c b/libsylph/socks.c new file mode 100644 index 0000000..b725ba7 --- /dev/null +++ b/libsylph/socks.c @@ -0,0 +1,267 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2014 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#ifdef G_OS_WIN32 +# include +# include +#endif + +#include "socket.h" +#include "socks.h" +#include "utils.h" + + +SocksInfo *socks_info_new(SocksType type, const gchar *proxy_host, + gushort proxy_port, const gchar *proxy_name, + const gchar *proxy_pass) +{ + SocksInfo *socks_info; + + socks_info = g_new0(SocksInfo, 1); + socks_info->type = type; + socks_info->proxy_host = g_strdup(proxy_host); + socks_info->proxy_port = proxy_port; + socks_info->proxy_name = g_strdup(proxy_name); + socks_info->proxy_pass = g_strdup(proxy_pass); + + return socks_info; +} + +void socks_info_free(SocksInfo *socks_info) +{ + if (!socks_info) + return; + g_free(socks_info->proxy_host); + g_free(socks_info->proxy_name); + g_free(socks_info->proxy_pass); + g_free(socks_info); +} + +gint socks_connect(SockInfo *sock, const gchar *hostname, gushort port, + SocksInfo *socks_info) +{ + g_return_val_if_fail(sock != NULL, -1); + g_return_val_if_fail(hostname != NULL, -1); + g_return_val_if_fail(socks_info != NULL, -1); + + debug_print("socks_connect: connect to %s:%u via %s:%u\n", + hostname, port, + socks_info->proxy_host, socks_info->proxy_port); + + if (socks_info->type == SOCKS_SOCKS5) + return socks5_connect(sock, hostname, port, + socks_info->proxy_name, + socks_info->proxy_pass); + else if (socks_info->type == SOCKS_SOCKS4) + return socks4_connect(sock, hostname, port); + else + g_warning("socks_connect: unknown SOCKS type: %d\n", + socks_info->type); + + return -1; +} + +gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port) +{ + guchar socks_req[1024]; + struct hostent *hp; + + g_return_val_if_fail(sock != NULL, -1); + g_return_val_if_fail(hostname != NULL, -1); + + debug_print("socks4_connect: connect to %s:%u\n", hostname, port); + + socks_req[0] = 4; + socks_req[1] = 1; + *((gushort *)(socks_req + 2)) = htons(port); + + /* lookup */ + if ((hp = my_gethostbyname(hostname)) == NULL) { + g_warning("socks4_connect: cannot lookup host: %s", hostname); + return -1; + } + if (hp->h_length != 4) { + g_warning("socks4_connect: invalid address length for host: %s", hostname); + return -1; + } + memcpy(socks_req + 4, (guchar *)hp->h_addr, 4); + + /* userid (empty) */ + socks_req[8] = 0; + + if (sock_write_all(sock, (gchar *)socks_req, 9) != 9) { + g_warning("socks4_connect: SOCKS4 initial request write failed"); + return -1; + } + + if (sock_read(sock, (gchar *)socks_req, 8) != 8) { + g_warning("socks4_connect: SOCKS4 response read failed"); + return -1; + } + if (socks_req[0] != 0) { + g_warning("socks4_connect: SOCKS4 response has invalid version"); + return -1; + } + if (socks_req[1] != 90) { + g_warning("socks4_connect: SOCKS4 connection to %u.%u.%u.%u:%u failed. (%u)", socks_req[4], socks_req[5], socks_req[6], socks_req[7], ntohs(*(gushort *)(socks_req + 2)), socks_req[1]); + return -1; + } + + /* replace sock->hostname with endpoint */ + if (sock->hostname != hostname) { + g_free(sock->hostname); + sock->hostname = g_strdup(hostname); + sock->port = port; + } + + debug_print("socks4_connect: SOCKS4 connection to %s:%u successful.\n", hostname, port); + + return 0; +} + +gint socks5_connect(SockInfo *sock, const gchar *hostname, gushort port, + const gchar *proxy_name, const gchar *proxy_pass) +{ + guchar socks_req[1024]; + size_t len; + size_t size; + + g_return_val_if_fail(sock != NULL, -1); + g_return_val_if_fail(hostname != NULL, -1); + + debug_print("socks5_connect: connect to %s:%u\n", hostname, port); + + len = strlen(hostname); + if (len > 255) { + g_warning("socks5_connect: hostname too long"); + return -1; + } + + socks_req[0] = 5; + socks_req[1] = proxy_name ? 2 : 1; + socks_req[2] = 0; + socks_req[3] = 2; + + if (sock_write_all(sock, (gchar *)socks_req, 2 + socks_req[1]) != 2 + socks_req[1]) { + g_warning("socks5_connect: SOCKS5 initial request write failed"); + return -1; + } + + if (sock_read(sock, (gchar *)socks_req, 2) != 2) { + g_warning("socks5_connect: SOCKS5 response read failed"); + return -1; + } + if (socks_req[0] != 5) { + g_warning("socks5_connect: SOCKS5 response has invalid version"); + return -1; + } + if (socks_req[1] == 2) { + /* auth */ + size_t userlen, passlen; + gint reqlen; + + if (proxy_name && proxy_pass) { + userlen = strlen(proxy_name); + passlen = strlen(proxy_pass); + } else + userlen = passlen = 0; + + socks_req[0] = 1; + socks_req[1] = (guchar)userlen; + if (proxy_name && userlen > 0) + memcpy(socks_req + 2, proxy_name, userlen); + socks_req[2 + userlen] = (guchar)passlen; + if (proxy_pass && passlen > 0) + memcpy(socks_req + 2 + userlen + 1, proxy_pass, passlen); + + reqlen = 2 + userlen + 1 + passlen; + if (sock_write_all(sock, (gchar *)socks_req, reqlen) != reqlen) { + g_warning("socks5_connect: SOCKS5 auth write failed"); + return -1; + } + if (sock_read(sock, (gchar *)socks_req, 2) != 2) { + g_warning("socks5_connect: SOCKS5 auth response read failed"); + return -1; + } + if (socks_req[1] != 0) { + g_warning("socks5_connect: SOCKS5 authentication failed: user: %s (%u %u)", proxy_name ? proxy_name : "(none)", socks_req[0], socks_req[1]); + return -1; + } + } else if (socks_req[1] != 0) { + g_warning("socks5_connect: SOCKS5 reply (%u) error", socks_req[1]); + return -1; + } + + socks_req[0] = 5; + socks_req[1] = 1; + socks_req[2] = 0; + + socks_req[3] = 3; + socks_req[4] = (guchar)len; + memcpy(socks_req + 5, hostname, len); + *((gushort *)(socks_req + 5 + len)) = htons(port); + + if (sock_write_all(sock, (gchar *)socks_req, 5 + len + 2) != 5 + len + 2) { + g_warning("socks5_connect: SOCKS5 connect request write failed"); + return -1; + } + + if (sock_read(sock, (gchar *)socks_req, 10) != 10) { + g_warning("socks5_connect: SOCKS5 connect request response read failed"); + return -1; + } + if (socks_req[0] != 5) { + g_warning("socks5_connect: SOCKS5 response has invalid version"); + return -1; + } + if (socks_req[1] != 0) { + g_warning("socks5_connect: SOCKS5 connection to %u.%u.%u.%u:%u failed. (%u)", socks_req[4], socks_req[5], socks_req[6], socks_req[7], ntohs(*(gushort *)(socks_req + 8)), socks_req[1]); + return -1; + } + + size = 10; + if (socks_req[3] == 3) + size = 5 + socks_req[4] + 2; + else if (socks_req[3] == 4) + size = 4 + 16 + 2; + if (size > 10) { + size -= 10; + if (sock_read(sock, (gchar *)socks_req + 10, size) != size) { + g_warning("socks5_connect: SOCKS5 connect request response read failed"); + return -1; + } + } + + /* replace sock->hostname with endpoint */ + if (sock->hostname != hostname) { + g_free(sock->hostname); + sock->hostname = g_strdup(hostname); + sock->port = port; + } + + debug_print("socks5_connect: SOCKS5 connection to %s:%u successful.\n", hostname, port); + + return 0; +} diff --git a/libsylph/socks.h b/libsylph/socks.h new file mode 100644 index 0000000..afa34c5 --- /dev/null +++ b/libsylph/socks.h @@ -0,0 +1,59 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2010 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __SOCKS_H__ +#define __SOCKS_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "socket.h" + +typedef struct _SocksInfo SocksInfo; + +typedef enum { + SOCKS_SOCKS4, + SOCKS_SOCKS5 +} SocksType; + +struct _SocksInfo +{ + SocksType type; + gchar *proxy_host; + gushort proxy_port; + gchar *proxy_name; + gchar *proxy_pass; +}; + +SocksInfo *socks_info_new(SocksType type, const gchar *proxy_host, + gushort proxy_port, const gchar *proxy_name, + const gchar *proxy_pass); +void socks_info_free(SocksInfo *socks_info); + +gint socks_connect(SockInfo *sock, const gchar *hostname, gushort port, + SocksInfo *socks_info); + +gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port); +gint socks5_connect(SockInfo *sock, const gchar *hostname, gushort port, + const gchar *proxy_name, const gchar *proxy_pass); + +#endif /* __SOCKS_H__ */ diff --git a/libsylph/ssl.c b/libsylph/ssl.c new file mode 100644 index 0000000..8413925 --- /dev/null +++ b/libsylph/ssl.c @@ -0,0 +1,405 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2014 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if USE_SSL + +#include "defs.h" + +#include +#include + +#include "utils.h" +#include "ssl.h" +#include "ssl_hostname_validation.h" + +static SSL_CTX *ssl_ctx_SSLv23 = NULL; +static SSL_CTX *ssl_ctx_TLSv1 = NULL; + +static GSList *trust_list = NULL; +static GSList *tmp_trust_list = NULL; +static GSList *reject_list = NULL; + +static SSLVerifyFunc verify_ui_func = NULL; + +static gchar *find_certs_file(const gchar *certs_dir) +{ + gchar *certs_file; + +#define LOOK_FOR(crt) \ +{ \ + certs_file = g_strconcat(certs_dir, G_DIR_SEPARATOR_S, crt, NULL); \ + debug_print("looking for %s\n", certs_file); \ + if (is_file_exist(certs_file)) \ + return certs_file; \ + g_free(certs_file); \ +} + + if (certs_dir) { + LOOK_FOR("ca-certificates.crt"); + LOOK_FOR("ca-bundle.crt"); + LOOK_FOR("ca-root.crt"); + LOOK_FOR("certs.crt"); + LOOK_FOR("cert.pem"); + } + +#undef LOOK_FOR + + return NULL; +} + +void ssl_init(void) +{ + gchar *certs_file, *certs_dir; + FILE *fp; + + SSL_library_init(); + SSL_load_error_strings(); + + certs_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "certs", NULL); + if (!is_dir_exist(certs_dir)) { + debug_print("ssl_init(): %s doesn't exist, or not a directory.\n", + certs_dir); + g_free(certs_dir); +#ifdef G_OS_WIN32 + certs_dir = g_strconcat(get_startup_dir(), G_DIR_SEPARATOR_S + "etc" G_DIR_SEPARATOR_S + "ssl" G_DIR_SEPARATOR_S "certs", NULL); +#else + certs_dir = g_strdup("/etc/ssl/certs"); +#endif + if (!is_dir_exist(certs_dir)) { + debug_print("ssl_init(): %s doesn't exist, or not a directory.\n", + certs_dir); + g_free(certs_dir); + certs_dir = NULL; + } + } + if (certs_dir) + debug_print("ssl_init(): certs dir %s found.\n", certs_dir); + + certs_file = find_certs_file(get_rc_dir()); + + if (certs_dir && !certs_file) + certs_file = find_certs_file(certs_dir); + + if (!certs_file) { +#ifdef G_OS_WIN32 + certs_dir = g_strconcat(get_startup_dir(), + G_DIR_SEPARATOR_S "etc" + G_DIR_SEPARATOR_S "ssl", NULL); + certs_file = find_certs_file(certs_dir); + g_free(certs_dir); + certs_dir = NULL; + if (!certs_file) { + certs_dir = g_strconcat(get_startup_dir(), + G_DIR_SEPARATOR_S "etc", NULL); + certs_file = find_certs_file(certs_dir); + g_free(certs_dir); + certs_dir = NULL; + } +#else + certs_file = find_certs_file("/etc/ssl"); + if (!certs_file) + certs_file = find_certs_file("/etc"); +#endif + } + + if (certs_file) + debug_print("ssl_init(): certs file %s found.\n", certs_file); + + ssl_ctx_SSLv23 = SSL_CTX_new(SSLv23_client_method()); + if (ssl_ctx_SSLv23 == NULL) { + debug_print(_("SSLv23 not available\n")); + } else { + debug_print(_("SSLv23 available\n")); + if ((certs_file || certs_dir) && + !SSL_CTX_load_verify_locations(ssl_ctx_SSLv23, certs_file, + certs_dir)) + g_warning("SSLv23 SSL_CTX_load_verify_locations failed.\n"); + } + + /* ssl_ctx_TLSv1 = SSL_CTX_new(TLSv1_client_method()); */ + ssl_ctx_TLSv1 = SSL_CTX_new(SSLv23_client_method()); + if (ssl_ctx_TLSv1 == NULL) { + debug_print(_("TLSv1 not available\n")); + } else { + debug_print(_("TLSv1 available\n")); + /* disable SSLv2/SSLv3 */ + SSL_CTX_set_options(ssl_ctx_TLSv1, + SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3); + if ((certs_file || certs_dir) && + !SSL_CTX_load_verify_locations(ssl_ctx_TLSv1, certs_file, + certs_dir)) + g_warning("TLSv1 SSL_CTX_load_verify_locations failed.\n"); + } + + g_free(certs_dir); + g_free(certs_file); + + certs_file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "trust.crt", + NULL); + if ((fp = g_fopen(certs_file, "rb")) != NULL) { + X509 *cert; + + debug_print("ssl_init(): reading trust.crt\n"); + + while ((cert = PEM_read_X509(fp, NULL, NULL, NULL)) != NULL) + trust_list = g_slist_append(trust_list, cert); + fclose(fp); + } + g_free(certs_file); +} + +void ssl_done(void) +{ + gchar *trust_file; + GSList *cur; + FILE *fp; + + if (trust_list) { + trust_file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + "trust.crt", NULL); + if ((fp = g_fopen(trust_file, "wb")) == NULL) { + FILE_OP_ERROR(trust_file, "fopen"); + } + for (cur = trust_list; cur != NULL; cur = cur->next) { + if (fp && !PEM_write_X509(fp, (X509 *)cur->data)) + g_warning("can't write X509 to PEM file: %s", + trust_file); + X509_free((X509 *)cur->data); + } + if (fp) + fclose(fp); + g_free(trust_file); + g_slist_free(trust_list); + trust_list = NULL; + } + for (cur = tmp_trust_list; cur != NULL; cur = cur->next) + X509_free((X509 *)cur->data); + g_slist_free(tmp_trust_list); + tmp_trust_list = NULL; + for (cur = reject_list; cur != NULL; cur = cur->next) + X509_free((X509 *)cur->data); + g_slist_free(reject_list); + reject_list = NULL; + + if (ssl_ctx_SSLv23) { + SSL_CTX_free(ssl_ctx_SSLv23); + ssl_ctx_SSLv23 = NULL; + } + + if (ssl_ctx_TLSv1) { + SSL_CTX_free(ssl_ctx_TLSv1); + ssl_ctx_TLSv1 = NULL; + } +} + +gboolean ssl_init_socket(SockInfo *sockinfo) +{ + return ssl_init_socket_with_method(sockinfo, SSL_METHOD_SSLv23); +} + +static gint x509_cmp_func(gconstpointer a, gconstpointer b) +{ + const X509 *xa = a; + const X509 *xb = b; + + return X509_cmp(xa, xb); +} + +gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method) +{ + X509 *server_cert; + gint err, ret; + + switch (method) { + case SSL_METHOD_SSLv23: + if (!ssl_ctx_SSLv23) { + g_warning(_("SSL method not available\n")); + return FALSE; + } + sockinfo->ssl = SSL_new(ssl_ctx_SSLv23); + break; + case SSL_METHOD_TLSv1: + if (!ssl_ctx_TLSv1) { + g_warning(_("SSL method not available\n")); + return FALSE; + } + sockinfo->ssl = SSL_new(ssl_ctx_TLSv1); + break; + default: + g_warning(_("Unknown SSL method *PROGRAM BUG*\n")); + return FALSE; + break; + } + + if (sockinfo->ssl == NULL) { + g_warning(_("Error creating ssl context\n")); + return FALSE; + } + + SSL_set_fd(sockinfo->ssl, sockinfo->sock); + while ((ret = SSL_connect(sockinfo->ssl)) != 1) { + err = SSL_get_error(sockinfo->ssl, ret); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + g_usleep(100000); + g_warning("SSL_connect(): try again\n"); + continue; + } + g_warning("SSL_connect() failed with error %d, ret = %d (%s)\n", + err, ret, ERR_error_string(ERR_get_error(), NULL)); + return FALSE; + } + + /* Get the cipher */ + + debug_print(_("SSL connection using %s\n"), + SSL_get_cipher(sockinfo->ssl)); + debug_print("SSL protocol version: %s\n", + SSL_get_version(sockinfo->ssl)); + + /* Get server's certificate (note: beware of dynamic allocation) */ + + if ((server_cert = SSL_get_peer_certificate(sockinfo->ssl)) != NULL) { + glong verify_result; + gboolean expired = FALSE; + + if (get_debug_mode()) { + gchar *str; + guchar keyid[EVP_MAX_MD_SIZE]; + gchar keyidstr[EVP_MAX_MD_SIZE * 3 + 1] = ""; + guint keyidlen = 0; + gint i; + + debug_print(_("Server certificate:\n")); + + if ((str = X509_NAME_oneline(X509_get_subject_name(server_cert), 0, 0)) != NULL) { + debug_print(_(" Subject: %s\n"), str); + OPENSSL_free(str); + } + + if ((str = X509_NAME_oneline(X509_get_issuer_name(server_cert), 0, 0)) != NULL) { + debug_print(_(" Issuer: %s\n"), str); + OPENSSL_free(str); + } + if (X509_digest(server_cert, EVP_sha1(), keyid, &keyidlen)) { + for (i = 0; i < keyidlen; i++) + g_snprintf(keyidstr + i * 3, 4, "%02x:", keyid[i]); + keyidstr[keyidlen * 3 - 1] = '\0'; + debug_print(" SHA1 fingerprint: %s\n", keyidstr); + } + if (X509_digest(server_cert, EVP_md5(), keyid, &keyidlen)) { + for (i = 0; i < keyidlen; i++) + g_snprintf(keyidstr + i * 3, 4, "%02x:", keyid[i]); + keyidstr[keyidlen * 3 - 1] = '\0'; + debug_print(" MD5 fingerprint: %s\n", keyidstr); + } + } + + verify_result = SSL_get_verify_result(sockinfo->ssl); + if (verify_result == X509_V_OK) { + debug_print("SSL certificate verify OK\n"); + if (ssl_validate_hostname(sockinfo->hostname, server_cert) == SSL_HOSTNAME_MATCH_FOUND) { + debug_print("SSL certificate hostname validation OK\n"); + X509_free(server_cert); + return TRUE; + } else { + verify_result = X509_V_ERR_APPLICATION_VERIFICATION; + } + } + + if (verify_result == X509_V_ERR_CERT_HAS_EXPIRED) { + log_message("SSL certificate of %s has expired\n", sockinfo->hostname); + expired = TRUE; + } else if (g_slist_find_custom(trust_list, server_cert, + x509_cmp_func) || + g_slist_find_custom(tmp_trust_list, server_cert, + x509_cmp_func)) { + log_message("SSL certificate of %s previously accepted\n", sockinfo->hostname); + X509_free(server_cert); + return TRUE; + } else if (g_slist_find_custom(reject_list, server_cert, + x509_cmp_func)) { + log_message("SSL certificate of %s previously rejected\n", sockinfo->hostname); + X509_free(server_cert); + return FALSE; + } + + if (verify_result == X509_V_ERR_APPLICATION_VERIFICATION) { + g_warning("%s: SSL hostname validation failed\n", + sockinfo->hostname); + } else { + g_warning("%s: SSL certificate verify failed (%ld: %s)\n", + sockinfo->hostname, verify_result, + X509_verify_cert_error_string(verify_result)); + } + + if (verify_ui_func) { + gint res; + + res = verify_ui_func(sockinfo, sockinfo->hostname, + server_cert, verify_result); + /* 0: accept 1: temporarily accept -1: reject */ + if (res < 0) { + debug_print("SSL certificate of %s rejected\n", + sockinfo->hostname); +#if 0 + reject_list = g_slist_prepend + (reject_list, X509_dup(server_cert)); +#endif + X509_free(server_cert); + return FALSE; + } else if (res > 0) { + debug_print("Temporarily accept SSL certificate of %s\n", sockinfo->hostname); + if (!expired) + tmp_trust_list = g_slist_prepend(tmp_trust_list, X509_dup(server_cert)); + } else { + debug_print("Permanently accept SSL certificate of %s\n", sockinfo->hostname); + if (!expired) + trust_list = g_slist_prepend(trust_list, X509_dup(server_cert)); + } + } + + X509_free(server_cert); + } else { + g_warning("%s: couldn't get SSL certificate\n", + sockinfo->hostname); + return FALSE; + } + + return TRUE; +} + +void ssl_done_socket(SockInfo *sockinfo) +{ + if (sockinfo->ssl) { + SSL_free(sockinfo->ssl); + } +} + +void ssl_set_verify_func(SSLVerifyFunc func) +{ + verify_ui_func = func; +} + +#endif /* USE_SSL */ diff --git a/libsylph/ssl.h b/libsylph/ssl.h new file mode 100644 index 0000000..a9f690d --- /dev/null +++ b/libsylph/ssl.h @@ -0,0 +1,65 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2006 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __SSL_H__ +#define __SSL_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if USE_SSL + +#include +#include +#include +#include +#include +#include + +#include "socket.h" + +typedef enum { + SSL_METHOD_SSLv23, + SSL_METHOD_TLSv1 +} SSLMethod; + +typedef enum { + SSL_NONE, + SSL_TUNNEL, + SSL_STARTTLS +} SSLType; + +typedef gint (*SSLVerifyFunc) (SockInfo *sockinfo, + const gchar *hostname, + X509 *server_cert, + glong verify_result); + +void ssl_init (void); +void ssl_done (void); +gboolean ssl_init_socket (SockInfo *sockinfo); +gboolean ssl_init_socket_with_method (SockInfo *sockinfo, + SSLMethod method); +void ssl_done_socket (SockInfo *sockinfo); + +void ssl_set_verify_func (SSLVerifyFunc func); + +#endif /* USE_SSL */ + +#endif /* __SSL_H__ */ diff --git a/libsylph/ssl_hostname_validation.c b/libsylph/ssl_hostname_validation.c new file mode 100644 index 0000000..cb2cea9 --- /dev/null +++ b/libsylph/ssl_hostname_validation.c @@ -0,0 +1,282 @@ +/* + * Helper functions to perform basic hostname validation using OpenSSL. + * + * Copyright (C) 2012, iSEC Partners. + * + * 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. + * + * Author: Alban Diquet + * + * https://github.com/iSECPartners/ssl-conservatory + * + * Modified naming convention to match LibSylph. + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if USE_SSL + +#include + +#include +#include +#include + +#include "utils.h" +#include "ssl_hostname_validation.h" + + +#define HOSTNAME_MAX_SIZE 255 + + +/* The following host_match() function is based on cURL/libcurl code. */ + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/*************************************************************************** + * + * COPYRIGHT AND PERMISSION NOTICE + * + * Copyright (c) 1996 - 2014, Daniel Stenberg, . + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * 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 OF THIRD PARTY RIGHTS. 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. + * + * Except as contained in this notice, the name of a copyright holder shall not + * be used in advertising or otherwise to promote the sale, use or other dealings + * in this Software without prior written authorization of the copyright holder. + * + ***************************************************************************/ + +/** +* Match a hostname against a wildcard pattern. +* E.g. +* "foo.host.com" matches "*.host.com". +* +* We use the matching rule described in RFC6125, section 6.4.3. +* http://tools.ietf.org/html/rfc6125#section-6.4.3 +*/ + +static int host_match(const char *hostname, const char *pattern) +{ + const char *pattern_label_end, *pattern_wildcard, *hostname_label_end; + size_t prefixlen, suffixlen; + + if (!pattern || !*pattern || !hostname || !*hostname) + return SSL_HOSTNAME_MATCH_NOT_FOUND; + + /* trivial case */ + if (g_ascii_strcasecmp(pattern, hostname) == 0) + return SSL_HOSTNAME_MATCH_FOUND; + + pattern_wildcard = strchr(pattern, '*'); + if (pattern_wildcard == NULL) { + return g_ascii_strcasecmp(pattern, hostname) == 0 ? + SSL_HOSTNAME_MATCH_FOUND : SSL_HOSTNAME_MATCH_NOT_FOUND; + } + + /* We require at least 2 dots in pattern to avoid too wide wildcard + match. */ + pattern_label_end = strchr(pattern, '.'); + if (pattern_label_end == NULL || + strchr(pattern_label_end + 1, '.') == NULL || + pattern_wildcard > pattern_label_end || + g_ascii_strncasecmp(pattern, "xn--", 4) == 0) { + return g_ascii_strcasecmp(pattern, hostname) == 0 ? + SSL_HOSTNAME_MATCH_FOUND : SSL_HOSTNAME_MATCH_NOT_FOUND; + } + + hostname_label_end = strchr(hostname, '.'); + if (hostname_label_end == NULL || + g_ascii_strcasecmp(pattern_label_end, hostname_label_end) != 0) + return SSL_HOSTNAME_MATCH_NOT_FOUND; + + /* The wildcard must match at least one character, so the left-most + label of the hostname is at least as large as the left-most label + of the pattern. */ + if (hostname_label_end - hostname < pattern_label_end - pattern) + return SSL_HOSTNAME_MATCH_NOT_FOUND; + + prefixlen = pattern_wildcard - pattern; + suffixlen = pattern_label_end - (pattern_wildcard + 1); + return g_ascii_strncasecmp(pattern, hostname, prefixlen) == 0 && + g_ascii_strncasecmp(pattern_wildcard + 1, hostname_label_end - suffixlen, suffixlen) == 0 ? + SSL_HOSTNAME_MATCH_FOUND : SSL_HOSTNAME_MATCH_NOT_FOUND; +} + + +/** +* Tries to find a match for hostname in the certificate's Common Name field. +* +* Returns MatchFound if a match was found. +* Returns MatchNotFound if no matches were found. +* Returns MalformedCertificate if the Common Name had a NUL character embedded in it. +* Returns Error if the Common Name could not be extracted. +*/ +static SSLHostnameValidationResult matches_common_name(const char *hostname, const X509 *server_cert) { + int common_name_loc = -1; + X509_NAME_ENTRY *common_name_entry = NULL; + ASN1_STRING *common_name_asn1 = NULL; + char *common_name_str = NULL; + + // Find the position of the CN field in the Subject field of the certificate + common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) server_cert), NID_commonName, -1); + if (common_name_loc < 0) { + return SSL_HOSTNAME_ERROR; + } + + // Extract the CN field + common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *) server_cert), common_name_loc); + if (common_name_entry == NULL) { + return SSL_HOSTNAME_ERROR; + } + + // Convert the CN field to a C string + common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); + if (common_name_asn1 == NULL) { + return SSL_HOSTNAME_ERROR; + } + common_name_str = (char *) ASN1_STRING_data(common_name_asn1); + + debug_print("matches_common_name: %s\n", common_name_str); + + // Make sure there isn't an embedded NUL character in the CN + if (ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) { + return SSL_HOSTNAME_MALFORMED_CERTIFICATE; + } + + // Compare expected hostname with the CN + return host_match(hostname, common_name_str); +} + + +/** +* Tries to find a match for hostname in the certificate's Subject Alternative Name extension. +* +* Returns MatchFound if a match was found. +* Returns MatchNotFound if no matches were found. +* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it. +* Returns NoSANPresent if the SAN extension was not present in the certificate. +*/ +static SSLHostnameValidationResult matches_subject_alternative_name(const char *hostname, const X509 *server_cert) { + SSLHostnameValidationResult result = SSL_HOSTNAME_MATCH_NOT_FOUND; + int i; + int san_names_nb = -1; + STACK_OF(GENERAL_NAME) *san_names = NULL; + + // Try to extract the names within the SAN extension from the certificate + san_names = X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL); + if (san_names == NULL) { + return SSL_HOSTNAME_NO_SAN_PRESENT; + } + san_names_nb = sk_GENERAL_NAME_num(san_names); + + // Check each name within the extension + for (i=0; itype == GEN_DNS) { + // Current name is a DNS name, let's check it + char *dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName); + + debug_print("matches_subject_alternative_name: %s\n", dns_name); + + // Make sure there isn't an embedded NUL character in the DNS name + if (ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) { + result = SSL_HOSTNAME_MALFORMED_CERTIFICATE; + break; + } + else { // Compare expected hostname with the DNS name + if (host_match(hostname, dns_name) == SSL_HOSTNAME_MATCH_FOUND) { + result = SSL_HOSTNAME_MATCH_FOUND; + break; + } + } + } + } + sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); + + return result; +} + + +/** +* Validates the server's identity by looking for the expected hostname in the +* server's certificate. As described in RFC 6125, it first tries to find a match +* in the Subject Alternative Name extension. If the extension is not present in +* the certificate, it checks the Common Name instead. +* +* Returns MatchFound if a match was found. +* Returns MatchNotFound if no matches were found. +* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it. +* Returns Error if there was an error. +*/ +SSLHostnameValidationResult ssl_validate_hostname(const char *hostname, const X509 *server_cert) { + SSLHostnameValidationResult result; + + debug_print("ssl_validate_hostname: validating hostname: %s\n", hostname); + + if((hostname == NULL) || (server_cert == NULL)) + return SSL_HOSTNAME_ERROR; + + // First try the Subject Alternative Names extension + result = matches_subject_alternative_name(hostname, server_cert); + if (result == SSL_HOSTNAME_NO_SAN_PRESENT) { + // Extension was not found: try the Common Name + result = matches_common_name(hostname, server_cert); + } + + return result; +} + +#endif /* USE_SSL */ diff --git a/libsylph/ssl_hostname_validation.h b/libsylph/ssl_hostname_validation.h new file mode 100644 index 0000000..4a24ae3 --- /dev/null +++ b/libsylph/ssl_hostname_validation.h @@ -0,0 +1,66 @@ +/* + * Helper functions to perform basic hostname validation using OpenSSL. + * + * Copyright (C) 2012, iSEC Partners. + * + * 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. + * + * Author: Alban Diquet + * + * https://github.com/iSECPartners/ssl-conservatory + * + * Modified naming convention to match LibSylph. + * + */ + +#ifndef __SSL_HOSTNAME_VALIDATION_H__ +#define __SSL_HOSTNAME_VALIDATION_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if USE_SSL + +#include + +typedef enum { + SSL_HOSTNAME_MATCH_FOUND, + SSL_HOSTNAME_MATCH_NOT_FOUND, + SSL_HOSTNAME_NO_SAN_PRESENT, + SSL_HOSTNAME_MALFORMED_CERTIFICATE, + SSL_HOSTNAME_ERROR +} SSLHostnameValidationResult; + +/** +* Validates the server's identity by looking for the expected hostname in the +* server's certificate. As described in RFC 6125, it first tries to find a match +* in the Subject Alternative Name extension. If the extension is not present in +* the certificate, it checks the Common Name instead. +* +* Returns MatchFound if a match was found. +* Returns MatchNotFound if no matches were found. +* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it. +* Returns Error if there was an error. +*/ +SSLHostnameValidationResult ssl_validate_hostname(const char *hostname, const X509 *server_cert); + +#endif /* USE_SSL */ + +#endif /* __SSL_HOSTNAME_VALIDATION_H__ */ diff --git a/libsylph/stringtable.c b/libsylph/stringtable.c new file mode 100644 index 0000000..dbb1951 --- /dev/null +++ b/libsylph/stringtable.c @@ -0,0 +1,163 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "stringtable.h" +#include "utils.h" + +/* alfons - hashed string table (I wasn't content with GStringChunk; + * can't recall why :-) */ + +#if 0 +#define XXX_DEBUG \ + debug_print +#else +#define XXX_DEBUG \ + if (0) debug_print +#endif + +typedef struct StringEntry_ { + gint ref_count; + gchar *string; +} StringEntry; + +static StringEntry *string_entry_new(const gchar *str) +{ + StringEntry *entry; + + entry = g_new0(StringEntry, 1); + entry->ref_count = 1; + entry->string = g_strdup(str); + return entry; +} + +static void string_entry_free(StringEntry *entry) +{ + g_return_if_fail(entry != NULL); + + g_free(entry->string); + g_free(entry); +} + +StringTable *string_table_new(void) +{ + StringTable *strtable; + + strtable = g_new0(StringTable, 1); + g_return_val_if_fail(strtable != NULL, NULL); + strtable->hash_table = g_hash_table_new(g_str_hash, g_str_equal); + g_return_val_if_fail(strtable->hash_table, NULL); + return strtable; +} + +gchar *string_table_lookup_string(StringTable *table, const gchar *str) +{ + StringEntry *entry; + + entry = g_hash_table_lookup(table->hash_table, str); + + if (entry) { + return entry->string; + } else { + return NULL; + } +} + +gchar *string_table_insert_string(StringTable *table, const gchar *str) +{ + StringEntry *entry; + + entry = g_hash_table_lookup(table->hash_table, str); + + if (entry) { + entry->ref_count++; + XXX_DEBUG ("ref++ for %s (%d)\n", entry->string, + entry->ref_count); + } else { + entry = string_entry_new(str); + XXX_DEBUG ("inserting %s\n", str); + /* insert entry->string instead of str, since it can be + * invalid pointer after this. */ + g_hash_table_insert(table->hash_table, entry->string, entry); + } + + return entry->string; +} + +void string_table_free_string(StringTable *table, const gchar *str) +{ + StringEntry *entry; + + entry = g_hash_table_lookup(table->hash_table, str); + + if (entry) { + entry->ref_count--; + if (entry->ref_count <= 0) { + XXX_DEBUG ("refcount of string %s dropped to zero\n", + entry->string); + g_hash_table_remove(table->hash_table, str); + string_entry_free(entry); + } else { + XXX_DEBUG ("ref-- for %s (%d)\n", entry->string, + entry->ref_count); + } + } +} + +static gboolean string_table_remove_for_each_fn(gchar *key, StringEntry *entry, + gpointer user_data) +{ + g_return_val_if_fail(key != NULL, TRUE); + g_return_val_if_fail(entry != NULL, TRUE); + + string_entry_free(entry); + + return TRUE; +} + +void string_table_free(StringTable *table) +{ + g_return_if_fail(table != NULL); + g_return_if_fail(table->hash_table != NULL); + + g_hash_table_foreach_remove(table->hash_table, + (GHRFunc)string_table_remove_for_each_fn, + NULL); + g_hash_table_destroy(table->hash_table); + g_free(table); +} + +static void string_table_stats_for_each_fn(gchar *key, StringEntry *entry, + guint *totals) +{ + if (entry->ref_count > 1) { + *totals += strlen(key) * (entry->ref_count - 1); + } +} + +void string_table_get_stats(StringTable *table) +{ + guint totals = 0; + + g_hash_table_foreach(table->hash_table, + (GHFunc)string_table_stats_for_each_fn, &totals); + XXX_DEBUG ("TOTAL UNSPILLED %d (%dK)\n", totals, totals / 1024); +} diff --git a/libsylph/stringtable.h b/libsylph/stringtable.h new file mode 100644 index 0000000..6f06d3c --- /dev/null +++ b/libsylph/stringtable.h @@ -0,0 +1,38 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef STRINGTABLE_H__ +#define STRINGTABLE_H__ + +#include + +typedef struct { + GHashTable *hash_table; +} StringTable; + +StringTable *string_table_new (void); +void string_table_free (StringTable *table); + +gchar *string_table_lookup_string (StringTable *table, const gchar *str); +gchar *string_table_insert_string (StringTable *table, const gchar *str); +void string_table_free_string (StringTable *table, const gchar *str); + +void string_table_get_stats (StringTable *table); + +#endif /* STRINGTABLE_H__ */ diff --git a/libsylph/syl-marshal.c b/libsylph/syl-marshal.c new file mode 100644 index 0000000..3d17749 --- /dev/null +++ b/libsylph/syl-marshal.c @@ -0,0 +1,131 @@ + +#include + + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_char (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +/* VOID:VOID (syl-marshal.list:1) */ + +/* VOID:POINTER,STRING,UINT (syl-marshal.list:2) */ +void +syl_marshal_VOID__POINTER_STRING_UINT (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__POINTER_STRING_UINT) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + guint arg_3, + gpointer data2); + register GMarshalFunc_VOID__POINTER_STRING_UINT callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 4); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__POINTER_STRING_UINT) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_string (param_values + 2), + g_marshal_value_peek_uint (param_values + 3), + data2); +} + +/* VOID:POINTER (syl-marshal.list:3) */ + +/* VOID:POINTER,STRING,STRING (syl-marshal.list:4) */ +void +syl_marshal_VOID__POINTER_STRING_STRING (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__POINTER_STRING_STRING) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + gpointer arg_3, + gpointer data2); + register GMarshalFunc_VOID__POINTER_STRING_STRING callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 4); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__POINTER_STRING_STRING) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_string (param_values + 2), + g_marshal_value_peek_string (param_values + 3), + data2); +} + diff --git a/libsylph/syl-marshal.h b/libsylph/syl-marshal.h new file mode 100644 index 0000000..5bf8714 --- /dev/null +++ b/libsylph/syl-marshal.h @@ -0,0 +1,34 @@ + +#ifndef __syl_marshal_MARSHAL_H__ +#define __syl_marshal_MARSHAL_H__ + +#include + +G_BEGIN_DECLS + +/* VOID:VOID (syl-marshal.list:1) */ +#define syl_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID + +/* VOID:POINTER,STRING,UINT (syl-marshal.list:2) */ +extern void syl_marshal_VOID__POINTER_STRING_UINT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID:POINTER (syl-marshal.list:3) */ +#define syl_marshal_VOID__POINTER g_cclosure_marshal_VOID__POINTER + +/* VOID:POINTER,STRING,STRING (syl-marshal.list:4) */ +extern void syl_marshal_VOID__POINTER_STRING_STRING (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +G_END_DECLS + +#endif /* __syl_marshal_MARSHAL_H__ */ + diff --git a/libsylph/syl-marshal.list b/libsylph/syl-marshal.list new file mode 100644 index 0000000..983f5ad --- /dev/null +++ b/libsylph/syl-marshal.list @@ -0,0 +1,4 @@ +VOID:VOID +VOID:POINTER,STRING,UINT +VOID:POINTER +VOID:POINTER,STRING,STRING diff --git a/libsylph/sylmain.c b/libsylph/sylmain.c new file mode 100644 index 0000000..b753b3c --- /dev/null +++ b/libsylph/sylmain.c @@ -0,0 +1,342 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2010 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include + +#ifdef G_OS_UNIX +# include +#endif + +#if HAVE_LOCALE_H +# include +#endif + +#include "sylmain.h" +#include "syl-marshal.h" +#include "prefs_common.h" +#include "account.h" +#include "filter.h" +#include "folder.h" +#include "socket.h" +#include "codeconv.h" +#include "utils.h" + +#if USE_SSL +# include "ssl.h" +#endif + +#ifndef PACKAGE +# define PACKAGE GETTEXT_PACKAGE +#endif + +G_DEFINE_TYPE(SylApp, syl_app, G_TYPE_OBJECT); + +enum { + INIT_DONE, + APP_EXIT, + APP_FORCE_EXIT, + ADD_MSG, + REMOVE_MSG, + REMOVE_ALL_MSG, + REMOVE_FOLDER, + MOVE_FOLDER, + FOLDERLIST_UPDATED, + ACCOUNT_UPDATED, + LAST_SIGNAL +}; + +static guint app_signals[LAST_SIGNAL] = { 0 }; + +static GObject *app = NULL; + + +static void syl_app_init(SylApp *self) +{ +} + +static void syl_app_class_init(SylAppClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + + app_signals[INIT_DONE] = + g_signal_new("init-done", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + syl_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + app_signals[APP_EXIT] = + g_signal_new("app-exit", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + syl_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + app_signals[APP_FORCE_EXIT] = + g_signal_new("app-force-exit", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + syl_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + app_signals[ADD_MSG] = + g_signal_new("add-msg", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + syl_marshal_VOID__POINTER_STRING_UINT, + G_TYPE_NONE, + 3, + G_TYPE_POINTER, + G_TYPE_STRING, + G_TYPE_UINT); + app_signals[REMOVE_MSG] = + g_signal_new("remove-msg", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + syl_marshal_VOID__POINTER_STRING_UINT, + G_TYPE_NONE, + 3, + G_TYPE_POINTER, + G_TYPE_STRING, + G_TYPE_UINT); + app_signals[REMOVE_ALL_MSG] = + g_signal_new("remove-all-msg", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + syl_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + app_signals[REMOVE_FOLDER] = + g_signal_new("remove-folder", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + syl_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + app_signals[MOVE_FOLDER] = + g_signal_new("move-folder", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + syl_marshal_VOID__POINTER_STRING_STRING, + G_TYPE_NONE, + 3, + G_TYPE_POINTER, + G_TYPE_STRING, + G_TYPE_STRING); + app_signals[FOLDERLIST_UPDATED] = + g_signal_new("folderlist-updated", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + syl_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + app_signals[ACCOUNT_UPDATED] = + g_signal_new("account-updated", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + syl_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +GObject *syl_app_create(void) +{ + if (!app) + app = g_object_new(SYL_TYPE_APP, NULL); + return app; +} + +GObject *syl_app_get(void) +{ + return app; +} + +void syl_init(void) +{ +#ifdef G_OS_WIN32 + gchar *newpath; + const gchar *lang_env; + + /* disable locale variable such as "LANG=1041" */ + +#define DISABLE_DIGIT_LOCALE(envstr) \ +{ \ + lang_env = g_getenv(envstr); \ + if (lang_env && g_ascii_isdigit(lang_env[0])) \ + g_unsetenv(envstr); \ +} + + DISABLE_DIGIT_LOCALE("LC_ALL"); + DISABLE_DIGIT_LOCALE("LANG"); + DISABLE_DIGIT_LOCALE("LC_CTYPE"); + DISABLE_DIGIT_LOCALE("LC_MESSAGES"); + +#undef DISABLE_DIGIT_LOCALE + + g_unsetenv("LANGUAGE"); +#endif /* G_OS_WIN32 */ + +#ifdef HAVE_LOCALE_H + setlocale(LC_ALL, ""); +#endif + + set_startup_dir(); + +#ifdef G_OS_WIN32 + /* include startup directory into %PATH% for GSpawn */ + newpath = g_strconcat(get_startup_dir(), ";", g_getenv("PATH"), NULL); + g_setenv("PATH", newpath, TRUE); + g_free(newpath); +#endif + +#ifdef ENABLE_NLS + syl_init_gettext(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + sock_init(); + +#ifdef G_OS_UNIX + /* ignore SIGPIPE signal for preventing sudden death of program */ + signal(SIGPIPE, SIG_IGN); +#endif +} + +#define MAKE_DIR_IF_NOT_EXIST(dir) \ +{ \ + if (!is_dir_exist(dir)) { \ + if (is_file_exist(dir)) { \ + g_warning("File '%s' already exists. " \ + "Can't create folder.", dir); \ + return -1; \ + } \ + if (make_dir(dir) < 0) \ + return -1; \ + } \ +} + +void syl_init_gettext(const gchar *package, const gchar *dirname) +{ +#ifdef ENABLE_NLS + if (g_path_is_absolute(dirname)) + bindtextdomain(package, dirname); + else { + gchar *locale_dir; + + locale_dir = g_strconcat(get_startup_dir(), G_DIR_SEPARATOR_S, + dirname, NULL); +#ifdef G_OS_WIN32 + { + gchar *locale_dir_; + + locale_dir_ = g_locale_from_utf8(locale_dir, -1, + NULL, NULL, NULL); + if (locale_dir_) { + g_free(locale_dir); + locale_dir = locale_dir_; + } + } +#endif /* G_OS_WIN32 */ + bindtextdomain(package, locale_dir); + g_free(locale_dir); + } + + bind_textdomain_codeset(package, CS_UTF_8); +#endif /* ENABLE_NLS */ +} + +gint syl_setup_rc_dir(void) +{ + if (!is_dir_exist(get_rc_dir())) { + if (make_dir_hier(get_rc_dir()) < 0) + return -1; + } + + MAKE_DIR_IF_NOT_EXIST(get_mail_base_dir()); + + CHDIR_RETURN_VAL_IF_FAIL(get_rc_dir(), -1); + + MAKE_DIR_IF_NOT_EXIST(get_imap_cache_dir()); + MAKE_DIR_IF_NOT_EXIST(get_news_cache_dir()); + MAKE_DIR_IF_NOT_EXIST(get_mime_tmp_dir()); + MAKE_DIR_IF_NOT_EXIST(get_tmp_dir()); + MAKE_DIR_IF_NOT_EXIST(UIDL_DIR); + MAKE_DIR_IF_NOT_EXIST(PLUGIN_DIR); + + /* remove temporary files */ + remove_all_files(get_tmp_dir()); + remove_all_files(get_mime_tmp_dir()); + + return 0; +} + +void syl_save_all_state(void) +{ + folder_write_list(); + prefs_common_write_config(); + filter_write_config(); + account_write_config_all(); +} + +void syl_cleanup(void) +{ + /* remove temporary files */ + remove_all_files(get_tmp_dir()); + remove_all_files(get_mime_tmp_dir()); +#if GLIB_CHECK_VERSION(2, 6, 0) + g_log_set_default_handler(g_log_default_handler, NULL); +#endif + close_log_file(); + + sock_cleanup(); + + if (app) { + g_object_unref(app); + app = NULL; + } +} diff --git a/libsylph/sylmain.h b/libsylph/sylmain.h new file mode 100644 index 0000000..e09a604 --- /dev/null +++ b/libsylph/sylmain.h @@ -0,0 +1,57 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2009 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __SYLMAIN_H__ +#define __SYLMAIN_H__ + +#include +#include + +/* SylApp object */ + +#define SYL_TYPE_APP (syl_app_get_type()) +#define SYL_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SYL_TYPE_APP, SylApp)) +#define SYL_IS_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SYL_TYPE_APP)) +#define SYL_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SYL_TYPE_APP, SylAppClass)) +#define SYL_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SYL_TYPE_APP)) +#define SYL_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SYL_TYPE_APP, SylAppClass)) + +typedef struct _SylApp SylApp; +typedef struct _SylAppClass SylAppClass; + +struct _SylApp +{ + GObject parent_instance; +}; + +struct _SylAppClass +{ + GObjectClass parent_class; +}; + +GObject *syl_app_create (void); +GObject *syl_app_get (void); + +void syl_init (void); +void syl_init_gettext (const gchar *package, const gchar *dirname); +gint syl_setup_rc_dir (void); +void syl_save_all_state (void); +void syl_cleanup (void); + +#endif /* __SYLMAIN_H__ */ diff --git a/libsylph/unmime.c b/libsylph/unmime.c new file mode 100644 index 0000000..34a3d73 --- /dev/null +++ b/libsylph/unmime.c @@ -0,0 +1,135 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "codeconv.h" +#include "base64.h" +#include "quoted-printable.h" + +#define ENCODED_WORD_BEGIN "=?" +#define ENCODED_WORD_END "?=" + +/* Decodes headers based on RFC2045 and RFC2047. */ + +gchar *unmime_header(const gchar *encoded_str) +{ + const gchar *p = encoded_str; + const gchar *eword_begin_p, *encoding_begin_p, *text_begin_p, + *eword_end_p; + gchar charset[32]; + gchar encoding; + gchar *conv_str; + GString *outbuf; + gchar *out_str; + gsize out_len; + + outbuf = g_string_sized_new(strlen(encoded_str) * 2); + + while (*p != '\0') { + gchar *decoded_text = NULL; + gint len; + + eword_begin_p = strstr(p, ENCODED_WORD_BEGIN); + if (!eword_begin_p) { + g_string_append(outbuf, p); + break; + } + encoding_begin_p = strchr(eword_begin_p + 2, '?'); + if (!encoding_begin_p) { + g_string_append(outbuf, p); + break; + } + text_begin_p = strchr(encoding_begin_p + 1, '?'); + if (!text_begin_p) { + g_string_append(outbuf, p); + break; + } + eword_end_p = strstr(text_begin_p + 1, ENCODED_WORD_END); + if (!eword_end_p) { + g_string_append(outbuf, p); + break; + } + + if (p == encoded_str) { + g_string_append_len(outbuf, p, eword_begin_p - p); + p = eword_begin_p; + } else { + /* ignore spaces between encoded words */ + const gchar *sp; + + for (sp = p; sp < eword_begin_p; sp++) { + if (!g_ascii_isspace(*sp)) { + g_string_append_len + (outbuf, p, eword_begin_p - p); + p = eword_begin_p; + break; + } + } + } + + len = MIN(sizeof(charset) - 1, + encoding_begin_p - (eword_begin_p + 2)); + memcpy(charset, eword_begin_p + 2, len); + charset[len] = '\0'; + encoding = g_ascii_toupper(*(encoding_begin_p + 1)); + + if (encoding == 'B') { + decoded_text = g_malloc + (eword_end_p - (text_begin_p + 1) + 1); + len = base64_decode((guchar *)decoded_text, + text_begin_p + 1, + eword_end_p - (text_begin_p + 1)); + decoded_text[len] = '\0'; + } else if (encoding == 'Q') { + decoded_text = g_malloc + (eword_end_p - (text_begin_p + 1) + 1); + len = qp_decode_q_encoding + ((guchar *)decoded_text, text_begin_p + 1, + eword_end_p - (text_begin_p + 1)); + } else { + g_string_append_len(outbuf, p, eword_end_p + 2 - p); + p = eword_end_p + 2; + continue; + } + + /* convert to UTF-8 */ + conv_str = conv_codeset_strdup(decoded_text, charset, NULL); + if (!conv_str) + conv_str = conv_utf8todisp(decoded_text, NULL); + g_string_append(outbuf, conv_str); + g_free(conv_str); + + g_free(decoded_text); + + p = eword_end_p + 2; + } + + out_str = outbuf->str; + out_len = outbuf->len; + g_string_free(outbuf, FALSE); + + return g_realloc(out_str, out_len + 1); +} diff --git a/libsylph/unmime.h b/libsylph/unmime.h new file mode 100644 index 0000000..e87ba13 --- /dev/null +++ b/libsylph/unmime.h @@ -0,0 +1,27 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __UNMIME_H__ +#define __UNMIME_H__ + +#include + +gchar *unmime_header (const gchar *encoded_str); + +#endif /* __UNMIME_H__ */ diff --git a/libsylph/utils.c b/libsylph/utils.c new file mode 100644 index 0000000..aabce06 --- /dev/null +++ b/libsylph/utils.c @@ -0,0 +1,4849 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2017 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_SYS_WAIT_H +# include +#endif +#include +#include + +#ifdef G_OS_WIN32 +#ifndef WINVER +# define WINVER 0x0500 +#endif +# include +# include +# include +# include +# include +#endif + +#ifdef __APPLE__ +# include +#endif + +#include "utils.h" +#include "socket.h" + +#define BUFFSIZE 8192 + +static gboolean debug_mode = FALSE; + + +#if !GLIB_CHECK_VERSION(2, 7, 0) && !defined(G_OS_UNIX) +gint g_chdir(const gchar *path) +{ +#ifdef G_OS_WIN32 + if (G_WIN32_HAVE_WIDECHAR_API()) { + wchar_t *wpath; + gint retval; + gint save_errno; + + wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL); + if (wpath == NULL) { + errno = EINVAL; + return -1; + } + + retval = _wchdir(wpath); + save_errno = errno; + + g_free(wpath); + + errno = save_errno; + return retval; + } else { + gchar *cp_path; + gint retval; + gint save_errno; + + cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL); + if (cp_path == NULL) { + errno = EINVAL; + return -1; + } + + retval = chdir(cp_path); + save_errno = errno; + + g_free(cp_path); + + errno = save_errno; + return retval; + } +#else + return chdir(path); +#endif +} + +gint g_chmod(const gchar *path, gint mode) +{ +#ifdef G_OS_WIN32 + if (G_WIN32_HAVE_WIDECHAR_API()) { + wchar_t *wpath; + gint retval; + gint save_errno; + + wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL); + if (wpath == NULL) { + errno = EINVAL; + return -1; + } + + retval = _wchmod(wpath, mode); + save_errno = errno; + + g_free(wpath); + + errno = save_errno; + return retval; + } else { + gchar *cp_path; + gint retval; + gint save_errno; + + cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL); + if (cp_path == NULL) { + errno = EINVAL; + return -1; + } + + retval = chmod(cp_path, mode); + save_errno = errno; + + g_free(cp_path); + + errno = save_errno; + return retval; + } +#else + return chmod(path, mode); +#endif +} +#endif /* GLIB_CHECK_VERSION && G_OS_UNIX */ + +#ifndef G_OS_UNIX +gint syl_link(const gchar *src, const gchar *dest) +{ +#ifdef G_OS_WIN32 + wchar_t *wsrc; + wchar_t *wdest; + gint retval; + gint save_errno; + + wsrc = g_utf8_to_utf16(src, -1, NULL, NULL, NULL); + if (wsrc == NULL) { + errno = EINVAL; + return -1; + } + wdest = g_utf8_to_utf16(dest, -1, NULL, NULL, NULL); + if (wdest == NULL) { + g_free(wsrc); + errno = EINVAL; + return -1; + } + + errno = 0; + if (CreateHardLinkW(wdest, wsrc, NULL)) { + retval = 0; + /* debug_print("hard link created: %s -> %s\n", src, dest); */ + } else { + retval = -1; + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + errno = ENOENT; break; + case ERROR_ACCESS_DENIED: + case ERROR_LOCK_VIOLATION: + case ERROR_SHARING_VIOLATION: + errno = EACCES; break; + case ERROR_NOT_SAME_DEVICE: + errno = EXDEV; break; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + errno = EEXIST; break; + case ERROR_TOO_MANY_LINKS: + errno = EMLINK; break; + default: + errno = EIO; break; + } + } + save_errno = errno; + + g_free(wdest); + g_free(wsrc); + + errno = save_errno; + return retval; +#else + return link(src, dest); +#endif +} +#endif /* !G_OS_UNIX */ + +void list_free_strings(GList *list) +{ + list = g_list_first(list); + + while (list != NULL) { + g_free(list->data); + list = list->next; + } +} + +void slist_free_strings(GSList *list) +{ + while (list != NULL) { + g_free(list->data); + list = list->next; + } +} + +static void hash_free_strings_func(gpointer key, gpointer value, gpointer data) +{ + g_free(key); +} + +void hash_free_strings(GHashTable *table) +{ + g_hash_table_foreach(table, hash_free_strings_func, NULL); +} + +static void hash_free_value_mem_func(gpointer key, gpointer value, + gpointer data) +{ + g_free(value); +} + +void hash_free_value_mem(GHashTable *table) +{ + g_hash_table_foreach(table, hash_free_value_mem_func, NULL); +} + +gint str_case_equal(gconstpointer v, gconstpointer v2) +{ + return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0; +} + +guint str_case_hash(gconstpointer key) +{ + const gchar *p = key; + guint h = *p; + + if (h) { + h = g_ascii_tolower(h); + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + g_ascii_tolower(*p); + } + + return h; +} + +void ptr_array_free_strings(GPtrArray *array) +{ + gint i; + gchar *str; + + g_return_if_fail(array != NULL); + + for (i = 0; i < array->len; i++) { + str = g_ptr_array_index(array, i); + g_free(str); + } +} + +gboolean str_find(const gchar *haystack, const gchar *needle) +{ + return strstr(haystack, needle) != NULL ? TRUE : FALSE; +} + +gboolean str_case_find(const gchar *haystack, const gchar *needle) +{ + return strcasestr(haystack, needle) != NULL ? TRUE : FALSE; +} + +gboolean str_find_equal(const gchar *haystack, const gchar *needle) +{ + return strcmp(haystack, needle) == 0; +} + +gboolean str_case_find_equal(const gchar *haystack, const gchar *needle) +{ + return g_ascii_strcasecmp(haystack, needle) == 0; +} + +gint to_number(const gchar *nstr) +{ + register const gchar *p; + + if (*nstr == '\0') return -1; + + for (p = nstr; *p != '\0'; p++) + if (!g_ascii_isdigit(*p)) return -1; + + return atoi(nstr); +} + +guint to_unumber(const gchar *nstr) +{ + register const gchar *p; + gulong val; + + if (*nstr == '\0') return 0; + + for (p = nstr; *p != '\0'; p++) + if (!g_ascii_isdigit(*p)) return 0; + + errno = 0; + val = strtoul(nstr, NULL, 10); + if (val == ULONG_MAX && errno != 0) + val = 0; + + return (guint)val; +} + +/* convert integer into string, + nstr must be not lower than 11 characters length */ +gchar *itos_buf(gchar *nstr, gint n) +{ + g_snprintf(nstr, 11, "%d", n); + return nstr; +} + +/* convert integer into string */ +gchar *itos(gint n) +{ + static gchar nstr[11]; + + return itos_buf(nstr, n); +} + +gchar *utos_buf(gchar *nstr, guint n) +{ + g_snprintf(nstr, 11, "%u", n); + return nstr; +} + +gchar *to_human_readable_buf(gchar *buf, size_t bufsize, gint64 size) +{ + if (size < 1024) + g_snprintf(buf, bufsize, "%dB", (gint)size); + else if ((size >> 10) < 1024) + g_snprintf(buf, bufsize, "%.1fKB", (gfloat)size / (1 << 10)); + else if ((size >> 20) < 1024) + g_snprintf(buf, bufsize, "%.2fMB", (gfloat)size / (1 << 20)); + else + g_snprintf(buf, bufsize, "%.2fGB", (gfloat)size / (1 << 30)); + + return buf; +} + +gchar *to_human_readable(gint64 size) +{ + static gchar str[16]; + + return to_human_readable_buf(str, sizeof(str), size); +} + +/* strcmp with NULL-checking */ +gint strcmp2(const gchar *s1, const gchar *s2) +{ + if (s1 == NULL || s2 == NULL) + return -1; + else + return strcmp(s1, s2); +} + +/* compare paths */ +gint path_cmp(const gchar *s1, const gchar *s2) +{ + gint len1, len2; +#ifdef G_OS_WIN32 + gchar *s1_, *s2_; +#endif + + if (s1 == NULL || s2 == NULL) return -1; + if (*s1 == '\0' || *s2 == '\0') return -1; + + len1 = strlen(s1); + len2 = strlen(s2); + +#ifdef G_OS_WIN32 + Xstrdup_a(s1_, s1, return -1); + Xstrdup_a(s2_, s2, return -1); + subst_char(s1_, '/', G_DIR_SEPARATOR); + subst_char(s2_, '/', G_DIR_SEPARATOR); + if (s1_[len1 - 1] == G_DIR_SEPARATOR) len1--; + if (s2_[len2 - 1] == G_DIR_SEPARATOR) len2--; + + return strncmp(s1_, s2_, MAX(len1, len2)); +#else + if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--; + if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--; + + return strncmp(s1, s2, MAX(len1, len2)); +#endif +} + +/* return TRUE if parent is equal to or ancestor of child */ +gboolean is_path_parent(const gchar *parent, const gchar *child) +{ + gint plen; + const gchar *base; + + g_return_val_if_fail(parent != NULL, FALSE); + g_return_val_if_fail(child != NULL, FALSE); + + plen = strlen(parent); + while (plen > 0 && G_IS_DIR_SEPARATOR(parent[plen - 1])) + plen--; + +#ifdef G_OS_WIN32 + if (!g_ascii_strncasecmp(parent, child, plen)) { +#else + if (!strncmp(parent, child, plen)) { +#endif + base = child + plen; + if (!G_IS_DIR_SEPARATOR(*base) && *base != '\0') + return FALSE; + return TRUE; + } + + return FALSE; +} + +/* remove trailing return code */ +gchar *strretchomp(gchar *str) +{ + register gchar *s; + + if (!*str) return str; + + for (s = str + strlen(str) - 1; + s >= str && (*s == '\n' || *s == '\r'); + s--) + *s = '\0'; + + return str; +} + +/* remove trailing character */ +gchar *strtailchomp(gchar *str, gchar tail_char) +{ + register gchar *s; + + if (!*str) return str; + if (tail_char == '\0') return str; + + for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--) + *s = '\0'; + + return str; +} + +/* remove CR (carriage return) */ +gchar *strcrchomp(gchar *str) +{ + register gchar *s; + + if (!*str) return str; + + s = str + strlen(str) - 1; + if (*s == '\n' && s > str && *(s - 1) == '\r') { + *(s - 1) = '\n'; + *s = '\0'; + } + + return str; +} + +/* Similar to `strstr' but this function ignores the case of both strings. */ +gchar *strcasestr(const gchar *haystack, const gchar *needle) +{ + register size_t haystack_len, needle_len; + + haystack_len = strlen(haystack); + needle_len = strlen(needle); + + if (haystack_len < needle_len || needle_len == 0) + return NULL; + + while (haystack_len >= needle_len) { + if (!g_ascii_strncasecmp(haystack, needle, needle_len)) + return (gchar *)haystack; + else { + haystack++; + haystack_len--; + } + } + + return NULL; +} + +gpointer my_memmem(gconstpointer haystack, size_t haystacklen, + gconstpointer needle, size_t needlelen) +{ + const gchar *haystack_ = (const gchar *)haystack; + const gchar *needle_ = (const gchar *)needle; + const gchar *haystack_cur = (const gchar *)haystack; + size_t haystack_left = haystacklen; + + if (needlelen == 1) + return memchr(haystack_, *needle_, haystacklen); + + while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left)) + != NULL) { + if (haystacklen - (haystack_cur - haystack_) < needlelen) + break; + if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0) + return (gpointer)haystack_cur; + else { + haystack_cur++; + haystack_left = haystacklen - (haystack_cur - haystack_); + } + } + + return NULL; +} + +/* Copy no more than N characters of SRC to DEST, with NULL terminating. */ +gchar *strncpy2(gchar *dest, const gchar *src, size_t n) +{ + register const gchar *s = src; + register gchar *d = dest; + + while (--n && *s) + *d++ = *s++; + *d = '\0'; + + return dest; +} + +/* Similar to g_str_has_suffix() but case-insensitive */ +gboolean str_has_suffix_case(const gchar *str, const gchar *suffix) +{ + size_t len, s_len; + + if (!str || !suffix) + return FALSE; + + len = strlen(str); + s_len = strlen(suffix); + + if (s_len > len) + return FALSE; + + return (g_ascii_strcasecmp(str + (len - s_len), suffix) == 0); +} + +gint str_find_format_times(const gchar *haystack, gchar ch) +{ + gint n = 0; + const gchar *p = haystack; + + while ((p = strchr(p, '%')) != NULL) { + ++p; + if (*p == '%') { + ++p; + } else if (*p == ch) { + ++p; + ++n; + } else + return -1; + } + + return n; +} + +/* Examine if next block is non-ASCII string */ +gboolean is_next_nonascii(const gchar *s) +{ + const gchar *p; + gboolean in_quote = FALSE; + + /* skip head space */ + for (p = s; *p != '\0' && g_ascii_isspace(*p); p++) + ; + while (*p != '\0') { + if (!in_quote && g_ascii_isspace(*p)) + break; + if (*p == '"') + in_quote ^= TRUE; + else if (*(guchar *)p > 127 || *(guchar *)p < 32) + return TRUE; + ++p; + } + + return FALSE; +} + +gint get_next_word_len(const gchar *s) +{ + const gchar *p = s; + gboolean in_quote = FALSE; + + while (*p != '\0') { + if (!in_quote && g_ascii_isspace(*p)) + break; + if (*p == '"') + in_quote ^= TRUE; + ++p; + } + + return p - s; +} + +/* compare subjects */ +gint subject_compare(const gchar *s1, const gchar *s2) +{ + gchar *str1, *str2; + + if (!s1 || !s2) return -1; + if (!*s1 || !*s2) return -1; + + Xstrdup_a(str1, s1, return -1); + Xstrdup_a(str2, s2, return -1); + + trim_subject_for_compare(str1); + trim_subject_for_compare(str2); + + if (!*str1 || !*str2) return -1; + + return strcmp(str1, str2); +} + +gint subject_compare_for_sort(const gchar *s1, const gchar *s2) +{ + gchar *str1, *str2; + + if (!s1 || !s2) return -1; + + Xstrdup_a(str1, s1, return -1); + Xstrdup_a(str2, s2, return -1); + + trim_subject_for_sort(str1); + trim_subject_for_sort(str2); + + return g_ascii_strcasecmp(str1, str2); +} + +void trim_subject_for_compare(gchar *str) +{ + gchar *srcp; + + eliminate_parenthesis(str, '[', ']'); + eliminate_parenthesis(str, '(', ')'); + g_strstrip(str); + + while (!g_ascii_strncasecmp(str, "Re:", 3)) { + srcp = str + 3; + while (g_ascii_isspace(*srcp)) srcp++; + memmove(str, srcp, strlen(srcp) + 1); + } +} + +void trim_subject_for_sort(gchar *str) +{ + gchar *srcp; + + g_strstrip(str); + + while (!g_ascii_strncasecmp(str, "Re:", 3)) { + srcp = str + 3; + while (g_ascii_isspace(*srcp)) srcp++; + memmove(str, srcp, strlen(srcp) + 1); + } +} + +void trim_subject(gchar *str) +{ + register gchar *srcp, *destp; + gchar op, cl; + gint in_brace; + + destp = str; + while (!g_ascii_strncasecmp(destp, "Re:", 3)) { + destp += 3; + while (g_ascii_isspace(*destp)) destp++; + } + + if (*destp == '[') { + op = '['; + cl = ']'; + } else if (*destp == '(') { + op = '('; + cl = ')'; + } else + return; + + srcp = destp + 1; + in_brace = 1; + while (*srcp) { + if (*srcp == op) + in_brace++; + else if (*srcp == cl) + in_brace--; + srcp++; + if (in_brace == 0) + break; + } + while (g_ascii_isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); +} + +void eliminate_parenthesis(gchar *str, gchar op, gchar cl) +{ + register gchar *srcp, *destp; + gint in_brace; + + srcp = destp = str; + + while ((destp = strchr(destp, op))) { + in_brace = 1; + srcp = destp + 1; + while (*srcp) { + if (*srcp == op) + in_brace++; + else if (*srcp == cl) + in_brace--; + srcp++; + if (in_brace == 0) + break; + } + while (g_ascii_isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); + } +} + +void extract_parenthesis(gchar *str, gchar op, gchar cl) +{ + register gchar *srcp, *destp; + gint in_brace; + + srcp = destp = str; + + while ((srcp = strchr(destp, op))) { + if (destp > str) + *destp++ = ' '; + memmove(destp, srcp + 1, strlen(srcp)); + in_brace = 1; + while(*destp) { + if (*destp == op) + in_brace++; + else if (*destp == cl) + in_brace--; + + if (in_brace == 0) + break; + + destp++; + } + } + *destp = '\0'; +} + +void extract_parenthesis_with_escape(gchar *str, gchar op, gchar cl) +{ + register gchar *srcp, *destp; + gint in_brace; + + srcp = destp = str; + + while ((srcp = strchr(srcp, op))) { + if (destp > str) + *destp++ = ' '; + ++srcp; + in_brace = 1; + while (*srcp) { + if (*srcp == op) + in_brace++; + else if (*srcp == cl) + in_brace--; + + if (in_brace == 0) + break; + + if (*srcp == '\\' && *(srcp + 1) != '\0') + ++srcp; + + *destp++ = *srcp++; + } + } + *destp = '\0'; +} + +void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr, + gchar op, gchar cl) +{ + register gchar *srcp, *destp; + gint in_brace; + gboolean in_quote = FALSE; + + srcp = destp = str; + + while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) { + if (destp > str) + *destp++ = ' '; + memmove(destp, srcp + 1, strlen(srcp)); + in_brace = 1; + while(*destp) { + if (*destp == op && !in_quote) + in_brace++; + else if (*destp == cl && !in_quote) + in_brace--; + else if (*destp == quote_chr) + in_quote ^= TRUE; + + if (in_brace == 0) + break; + + destp++; + } + } + *destp = '\0'; +} + +void eliminate_quote(gchar *str, gchar quote_chr) +{ + register gchar *srcp, *destp; + + srcp = destp = str; + + while ((destp = strchr(destp, quote_chr))) { + if ((srcp = strchr(destp + 1, quote_chr))) { + srcp++; + while (g_ascii_isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); + } else { + *destp = '\0'; + break; + } + } +} + +void extract_quote(gchar *str, gchar quote_chr) +{ + register gchar *p; + + if ((str = strchr(str, quote_chr))) { + if ((p = strchr(str + 1, quote_chr))) { + *p = '\0'; + memmove(str, str + 1, p - str); + } + } +} + +void extract_quote_with_escape(gchar *str, gchar quote_chr) +{ + register gchar *sp, *dp; + + if ((sp = strchr(str, quote_chr))) { + dp = sp; + ++sp; + while (*sp) { + if (*sp == quote_chr) + break; + else if (*sp == '\\' && *(sp + 1) != '\0') + ++sp; + + *dp++ = *sp++; + } + *dp = '\0'; + } +} + +void eliminate_address_comment(gchar *str) +{ + register gchar *srcp, *destp; + gint in_brace; + + srcp = destp = str; + + while ((destp = strchr(destp, '"'))) { + if ((srcp = strchr(destp + 1, '"'))) { + srcp++; + if (*srcp == '@') { + destp = srcp + 1; + } else { + while (g_ascii_isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); + } + } else { + *destp = '\0'; + break; + } + } + + srcp = destp = str; + + while ((destp = strchr_with_skip_quote(destp, '"', '('))) { + in_brace = 1; + srcp = destp + 1; + while (*srcp) { + if (*srcp == '(') + in_brace++; + else if (*srcp == ')') + in_brace--; + srcp++; + if (in_brace == 0) + break; + } + while (g_ascii_isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); + } +} + +gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c) +{ + gboolean in_quote = FALSE; + + while (*str) { + if (*str == c && !in_quote) + return (gchar *)str; + if (*str == quote_chr) + in_quote ^= TRUE; + str++; + } + + return NULL; +} + +gchar *strrchr_with_skip_quote(const gchar *str, gint quote_chr, gint c) +{ + gboolean in_quote = FALSE; + const gchar *p; + + p = str + strlen(str) - 1; + while (p >= str) { + if (*p == c && !in_quote) + return (gchar *)p; + if (*p == quote_chr) + in_quote ^= TRUE; + p--; + } + + return NULL; +} + +void extract_address(gchar *str) +{ + eliminate_address_comment(str); + if (strchr_with_skip_quote(str, '"', '<')) + extract_parenthesis_with_skip_quote(str, '"', '<', '>'); + g_strstrip(str); +} + +void extract_list_id_str(gchar *str) +{ + if (strchr_with_skip_quote(str, '"', '<')) + extract_parenthesis_with_skip_quote(str, '"', '<', '>'); + g_strstrip(str); +} + +gchar *extract_addresses(const gchar *str) +{ + GString *new_str; + GSList *addr_list, *cur; + + if (!str) + return NULL; + + addr_list = address_list_append(NULL, str); + + new_str = g_string_new(NULL); + + for (cur = addr_list; cur != NULL; cur = cur->next) { + g_string_append(new_str, (gchar *)cur->data); + if (cur->next) + g_string_append(new_str, ", "); + } + + slist_free_strings(addr_list); + g_slist_free(addr_list); + + return g_string_free(new_str, FALSE); +} + +gchar *normalize_address_field(const gchar *str) +{ + GString *new_str; + GSList *addr_list, *cur; + gchar *addr, *p, *q, *r; + gchar *ret_str; + + addr_list = address_list_append_orig(NULL, str); + + new_str = g_string_new(NULL); + + for (cur = addr_list; cur != NULL; cur = cur->next) { + p = addr = (gchar *)cur->data; + q = strchr_with_skip_quote(p, '"', '<'); + if (q && q > p) { + r = q - 1; + while (r > p && g_ascii_isspace(*r)) + --r; + g_string_append_len(new_str, p, r - p + 1); + g_string_append_c(new_str, ' '); + p = q; + } + if (*p == '<') { + q = strchr(p, '>'); + if (q) { + r = q + 1; + if (*r) { + while (g_ascii_isspace(*r)) + ++r; + g_string_append(new_str, r); + if (new_str->len > 0 && + !g_ascii_isspace + (new_str->str[new_str->len - 1])) + g_string_append_c(new_str, ' '); + } + g_string_append_len(new_str, p, q - p + 1); + } else { + g_string_append(new_str, p); + g_string_append_c(new_str, '>'); + } + } else + g_string_append(new_str, p); + + if (cur->next) + g_string_append(new_str, ", "); + } + + slist_free_strings(addr_list); + ret_str = new_str->str; + g_string_free(new_str, FALSE); + + return ret_str; +} + +gboolean address_equal(const gchar *addr1, const gchar *addr2) +{ + gchar *addr1_, *addr2_; + + if (!addr1 || !addr2) + return FALSE; + + Xstrdup_a(addr1_, addr1, return FALSE); + Xstrdup_a(addr2_, addr2, return FALSE); + + extract_address(addr1_); + extract_address(addr2_); + + return strcmp(addr1_, addr2_) == 0; +} + +GSList *address_list_append_orig(GSList *addr_list, const gchar *str) +{ + const gchar *p = str, *q; + gchar *addr; + + if (!str) return addr_list; + + while (*p) { + if (*p == ',' || g_ascii_isspace(*p)) { + ++p; + } else if ((q = strchr_with_skip_quote(p, '"', ','))) { + addr = g_strndup(p, q - p); + g_strstrip(addr); + addr_list = g_slist_append(addr_list, addr); + p = q + 1; + } else { + addr = g_strdup(p); + g_strstrip(addr); + addr_list = g_slist_append(addr_list, addr); + break; + } + } + + return addr_list; +} + +GSList *address_list_append(GSList *addr_list, const gchar *str) +{ + gchar *work; + gchar *workp; + + if (!str) return addr_list; + + Xstrdup_a(work, str, return addr_list); + + eliminate_address_comment(work); + workp = work; + + while (workp && *workp) { + gchar *p, *next; + + if ((p = strchr_with_skip_quote(workp, '"', ','))) { + *p = '\0'; + next = p + 1; + } else + next = NULL; + + if (strchr_with_skip_quote(workp, '"', '<')) + extract_parenthesis_with_skip_quote + (workp, '"', '<', '>'); + + g_strstrip(workp); + if (*workp) + addr_list = g_slist_append(addr_list, g_strdup(workp)); + + workp = next; + } + + return addr_list; +} + +GSList *references_list_prepend(GSList *msgid_list, const gchar *str) +{ + const gchar *strp; + + if (!str) return msgid_list; + strp = str; + + while (strp && *strp) { + const gchar *start, *end; + gchar *msgid; + + if ((start = strchr(strp, '<')) != NULL) { + end = strchr(start + 1, '>'); + if (!end) break; + } else + break; + + msgid = g_strndup(start + 1, end - start - 1); + g_strstrip(msgid); + if (*msgid) + msgid_list = g_slist_prepend(msgid_list, msgid); + else + g_free(msgid); + + strp = end + 1; + } + + return msgid_list; +} + +GSList *references_list_append(GSList *msgid_list, const gchar *str) +{ + GSList *list; + + list = references_list_prepend(NULL, str); + list = g_slist_reverse(list); + msgid_list = g_slist_concat(msgid_list, list); + + return msgid_list; +} + +GSList *newsgroup_list_append(GSList *group_list, const gchar *str) +{ + gchar *work; + gchar *workp; + + if (!str) return group_list; + + Xstrdup_a(work, str, return group_list); + + workp = work; + + while (workp && *workp) { + gchar *p, *next; + + if ((p = strchr_with_skip_quote(workp, '"', ','))) { + *p = '\0'; + next = p + 1; + } else + next = NULL; + + g_strstrip(workp); + if (*workp) + group_list = g_slist_append(group_list, + g_strdup(workp)); + + workp = next; + } + + return group_list; +} + +GList *add_history(GList *list, const gchar *str) +{ + GList *old; + + g_return_val_if_fail(str != NULL, list); + + old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2); + if (old) { + g_free(old->data); + list = g_list_remove(list, old->data); + } else if (g_list_length(list) >= MAX_HISTORY_SIZE) { + GList *last; + + last = g_list_last(list); + if (last) { + g_free(last->data); + list = g_list_remove(list, last->data); + } + } + + list = g_list_prepend(list, g_strdup(str)); + + return list; +} + +void remove_return(gchar *str) +{ + register gchar *p = str; + + while (*p) { + if (*p == '\n' || *p == '\r') + memmove(p, p + 1, strlen(p)); + else + p++; + } +} + +void remove_space(gchar *str) +{ + register gchar *p = str; + register gint spc; + + while (*p) { + spc = 0; + while (g_ascii_isspace(*(p + spc))) + spc++; + if (spc) + memmove(p, p + spc, strlen(p + spc) + 1); + else + p++; + } +} + +void unfold_line(gchar *str) +{ + register gchar *p = str; + register gint spc; + + while (*p) { + if (*p == '\n' || *p == '\r') { + *p++ = ' '; + spc = 0; + while (g_ascii_isspace(*(p + spc))) + spc++; + if (spc) + memmove(p, p + spc, strlen(p + spc) + 1); + } else + p++; + } +} + +void subst_char(gchar *str, gchar orig, gchar subst) +{ + register gchar *p = str; + + while (*p) { + if (*p == orig) + *p = subst; + p++; + } +} + +void subst_chars(gchar *str, gchar *orig, gchar subst) +{ + register gchar *p = str; + + while (*p) { + if (strchr(orig, *p) != NULL) + *p = subst; + ++p; + } +} + +void subst_null(gchar *str, gint len, gchar subst) +{ + register gchar *p = str; + + while (len--) { + if (*p == '\0') + *p = subst; + ++p; + } +} + +void subst_control(gchar *str, gchar subst) +{ + register gchar *p = str; + + while (*p) { + if (g_ascii_iscntrl(*p)) + *p = subst; + ++p; + } +} + +void subst_for_filename(gchar *str) +{ + subst_chars(str, " \t\r\n\"'\\/:;*?<>|", '_'); +} + +gchar *get_alt_filename(const gchar *filename, gint count) +{ + const gchar *ext; + gchar *alt_filename; + + ext = strrchr(filename, '.'); + + if (ext) { + gchar *base; + + base = g_strndup(filename, ext - filename); + alt_filename = g_strdup_printf("%s-%d%s", base, count, ext); + g_free(base); + } else + alt_filename = g_strdup_printf("%s-%d", filename, count); + + return alt_filename; +} + +gboolean is_header_line(const gchar *str) +{ + if (str[0] == ':') return FALSE; + + while (*str != '\0' && *str != ' ') { + if (*str == ':') + return TRUE; + str++; + } + + return FALSE; +} + +gboolean is_ascii_str(const gchar *str) +{ + const guchar *p = (const guchar *)str; + + while (*p != '\0') { + if (*p != '\t' && *p != ' ' && + *p != '\r' && *p != '\n' && + (*p < 32 || *p >= 127)) + return FALSE; + p++; + } + + return TRUE; +} + +gint get_quote_level(const gchar *str) +{ + const gchar *first_pos; + const gchar *last_pos; + const gchar *p = str; + gint quote_level = -1; + + /* speed up line processing by only searching to the last '>' */ + if ((first_pos = strchr(str, '>')) != NULL) { + /* skip a line if it contains a '<' before the initial '>' */ + if (memchr(str, '<', first_pos - str) != NULL) + return -1; + last_pos = strrchr(first_pos, '>'); + } else + return -1; + + while (p <= last_pos) { + while (p < last_pos) { + if (g_ascii_isspace(*p)) + p++; + else + break; + } + + if (*p == '>') + quote_level++; + else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) { + /* any characters are allowed except '-' and space */ + while (*p != '-' && *p != '>' && !g_ascii_isspace(*p) && + p < last_pos) + p++; + if (*p == '>') + quote_level++; + else + break; + } + + p++; + } + + return quote_level; +} + +gint check_line_length(const gchar *str, gint max_chars, gint *line) +{ + const gchar *p = str, *q; + gint cur_line = 0, len; + + while ((q = strchr(p, '\n')) != NULL) { + len = q - p + 1; + if (len > max_chars) { + if (line) + *line = cur_line; + return -1; + } + p = q + 1; + ++cur_line; + } + + len = strlen(p); + if (len > max_chars) { + if (line) + *line = cur_line; + return -1; + } + + return 0; +} + +gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle) +{ + register guint haystack_len, needle_len; + gboolean in_squote = FALSE, in_dquote = FALSE; + + haystack_len = strlen(haystack); + needle_len = strlen(needle); + + if (haystack_len < needle_len || needle_len == 0) + return NULL; + + while (haystack_len >= needle_len) { + if (!in_squote && !in_dquote && + !strncmp(haystack, needle, needle_len)) + return (gchar *)haystack; + + /* 'foo"bar"' -> foo"bar" + "foo'bar'" -> foo'bar' */ + if (*haystack == '\'') { + if (in_squote) + in_squote = FALSE; + else if (!in_dquote) + in_squote = TRUE; + } else if (*haystack == '\"') { + if (in_dquote) + in_dquote = FALSE; + else if (!in_squote) + in_dquote = TRUE; + } + + haystack++; + haystack_len--; + } + + return NULL; +} + +gchar *strcasestr_with_skip_quote(const gchar *haystack, const gchar *needle) +{ + register guint haystack_len, needle_len; + gboolean in_squote = FALSE, in_dquote = FALSE; + + haystack_len = strlen(haystack); + needle_len = strlen(needle); + + if (haystack_len < needle_len || needle_len == 0) + return NULL; + + while (haystack_len >= needle_len) { + if (!in_squote && !in_dquote && + !g_ascii_strncasecmp(haystack, needle, needle_len)) + return (gchar *)haystack; + + /* 'foo"bar"' -> foo"bar" + "foo'bar'" -> foo'bar' */ + if (*haystack == '\'') { + if (in_squote) + in_squote = FALSE; + else if (!in_dquote) + in_squote = TRUE; + } else if (*haystack == '\"') { + if (in_dquote) + in_dquote = FALSE; + else if (!in_squote) + in_dquote = TRUE; + } + + haystack++; + haystack_len--; + } + + return NULL; +} + +gchar *strchr_parenthesis_close(const gchar *str, gchar op, gchar cl) +{ + const gchar *p; + gchar quote_chr = '"'; + gint in_brace; + gboolean in_quote = FALSE; + + p = str; + + if ((p = strchr_with_skip_quote(p, quote_chr, op))) { + p++; + in_brace = 1; + while (*p) { + if (*p == op && !in_quote) + in_brace++; + else if (*p == cl && !in_quote) + in_brace--; + else if (*p == quote_chr) + in_quote ^= TRUE; + + if (in_brace == 0) + return (gchar *)p; + + p++; + } + } + + return NULL; +} + +gchar **strsplit_parenthesis(const gchar *str, gchar op, gchar cl, + gint max_tokens) +{ + GSList *string_list = NULL, *slist; + gchar **str_array; + const gchar *s_op, *s_cl; + guint i, n = 1; + + g_return_val_if_fail(str != NULL, NULL); + + if (max_tokens < 1) + max_tokens = G_MAXINT; + + s_op = strchr_with_skip_quote(str, '"', op); + if (!s_op) return NULL; + str = s_op; + s_cl = strchr_parenthesis_close(str, op, cl); + if (s_cl) { + do { + guint len; + gchar *new_string; + + str++; + len = s_cl - str; + new_string = g_new(gchar, len + 1); + strncpy(new_string, str, len); + new_string[len] = 0; + string_list = g_slist_prepend(string_list, new_string); + n++; + str = s_cl + 1; + + while (*str && g_ascii_isspace(*str)) str++; + if (*str != op) { + string_list = g_slist_prepend(string_list, + g_strdup("")); + n++; + s_op = strchr_with_skip_quote(str, '"', op); + if (!--max_tokens || !s_op) break; + str = s_op; + } else + s_op = str; + s_cl = strchr_parenthesis_close(str, op, cl); + } while (--max_tokens && s_cl); + } + + str_array = g_new(gchar*, n); + + i = n - 1; + str_array[i--] = NULL; + for (slist = string_list; slist; slist = slist->next) + str_array[i--] = slist->data; + + g_slist_free(string_list); + + return str_array; +} + +gchar **strsplit_with_quote(const gchar *str, const gchar *delim, + gint max_tokens) +{ + GSList *string_list = NULL, *slist; + gchar **str_array, *s, *new_str; + guint i, n = 1, len; + + g_return_val_if_fail(str != NULL, NULL); + g_return_val_if_fail(delim != NULL, NULL); + + if (max_tokens < 1) + max_tokens = G_MAXINT; + + s = strstr_with_skip_quote(str, delim); + if (s) { + guint delimiter_len = strlen(delim); + + do { + len = s - str; + new_str = g_strndup(str, len); + + if (new_str[0] == '\'' || new_str[0] == '\"') { + if (new_str[len - 1] == new_str[0]) { + new_str[len - 1] = '\0'; + memmove(new_str, new_str + 1, len - 1); + } + } + string_list = g_slist_prepend(string_list, new_str); + n++; + str = s + delimiter_len; + s = strstr_with_skip_quote(str, delim); + } while (--max_tokens && s); + } + + if (*str) { + new_str = g_strdup(str); + if (new_str[0] == '\'' || new_str[0] == '\"') { + len = strlen(str); + if (new_str[len - 1] == new_str[0]) { + new_str[len - 1] = '\0'; + memmove(new_str, new_str + 1, len - 1); + } + } + string_list = g_slist_prepend(string_list, new_str); + n++; + } + + str_array = g_new(gchar*, n); + + i = n - 1; + str_array[i--] = NULL; + for (slist = string_list; slist; slist = slist->next) + str_array[i--] = slist->data; + + g_slist_free(string_list); + + return str_array; +} + +gchar **strsplit_csv(const gchar *str, gchar delim, gint max_tokens) +{ + GSList *string_list = NULL, *slist; + gchar **str_array, *s, *new_str; + gchar *tmp, *tmpp, *p; + guint i, n = 1, len; + + g_return_val_if_fail(str != NULL, NULL); + + if (max_tokens < 1) + max_tokens = G_MAXINT; + + s = strchr_with_skip_quote(str, '"', delim); + if (s) { + do { + len = s - str; + tmpp = tmp = g_strndup(str, len); + + if (tmp[0] == '"' && tmp[len - 1] == tmp[0]) { + tmp[len - 1] = '\0'; + ++tmpp; + p = new_str = g_malloc(len - 1); + while (*tmpp) { + if (*tmpp == '"' && *(tmpp + 1) == '"') + ++tmpp; + *p++ = *tmpp++; + } + *p = '\0'; + g_free(tmp); + } else + new_str = tmp; + + string_list = g_slist_prepend(string_list, new_str); + n++; + str = s + 1; + s = strchr_with_skip_quote(str, '"', delim); + } while (--max_tokens && s); + } + + if (*str) { + len = strlen(str); + tmpp = tmp = g_strdup(str); + + if (tmp[0] == '"' && tmp[len - 1] == tmp[0]) { + tmp[len - 1] = '\0'; + ++tmpp; + p = new_str = g_malloc(len - 1); + while (*tmpp) { + if (*tmpp == '"' && *(tmpp + 1) == '"') + ++tmpp; + *p++ = *tmpp++; + } + *p = '\0'; + g_free(tmp); + } else + new_str = tmp; + + string_list = g_slist_prepend(string_list, new_str); + n++; + } + + str_array = g_new(gchar*, n); + + i = n - 1; + str_array[i--] = NULL; + for (slist = string_list; slist; slist = slist->next) + str_array[i--] = slist->data; + + g_slist_free(string_list); + + return str_array; +} + +/* Concatenate multiple strings into a CSV (TSV) record. + * If delimiter characters or double-quotes appear in the string, + it is enclosed by double quotes. + * Double quote characters are escaped by another double quote. + * Strings must not include line breaks in this implementation. + */ +gchar *strconcat_csv(gchar delim, const gchar *field1, ...) +{ + va_list args; + gchar *s; + GString *csv; + gint count = 0; + const gchar *p; + + g_return_val_if_fail(field1 != NULL, NULL); + + csv = g_string_new(""); + + va_start(args, field1); + s = (gchar *)field1; + while (s) { + gboolean add_quote = FALSE; + + if (count > 0) + g_string_append_c(csv, delim); + if (strchr(s, delim) != NULL || strchr(s, '"') != NULL) + add_quote = TRUE; + if (add_quote) + g_string_append_c(csv, '"'); + for (p = s; *p != '\0'; p++) { + if (*p == '"') + g_string_append_c(csv, '"'); + g_string_append_c(csv, *p); + } + if (add_quote) + g_string_append_c(csv, '"'); + count++; + s = va_arg(args, gchar*); + } + va_end(args); + + return g_string_free(csv, FALSE); +} + +gchar *get_abbrev_newsgroup_name(const gchar *group, gint len) +{ + gchar *abbrev_group; + gchar *ap; + const gchar *p = group; + const gchar *last; + + last = group + strlen(group); + abbrev_group = ap = g_malloc(strlen(group) + 1); + + while (*p) { + while (*p == '.') + *ap++ = *p++; + if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) { + *ap++ = *p++; + while (*p != '.') p++; + } else { + strcpy(ap, p); + return abbrev_group; + } + } + + *ap = '\0'; + return abbrev_group; +} + +gchar *trim_string(const gchar *str, gint len) +{ + const gchar *p = str; + gint mb_len; + gchar *new_str; + gint new_len = 0; + + if (!str) return NULL; + if (strlen(str) <= len) + return g_strdup(str); + if (g_utf8_validate(str, -1, NULL) == FALSE) + return g_strdup(str); + + while (*p != '\0') { + mb_len = g_utf8_skip[*(guchar *)p]; + if (mb_len == 0) + break; + else if (new_len + mb_len > len) + break; + + new_len += mb_len; + p += mb_len; + } + + Xstrndup_a(new_str, str, new_len, return g_strdup(str)); + return g_strconcat(new_str, "...", NULL); +} + +gchar *trim_string_before(const gchar *str, gint len) +{ + const gchar *p = str; + gint mb_len; + gint new_len; + + if (!str) return NULL; + if ((new_len = strlen(str)) <= len) + return g_strdup(str); + if (g_utf8_validate(str, -1, NULL) == FALSE) + return g_strdup(str); + + while (*p != '\0') { + mb_len = g_utf8_skip[*(guchar *)p]; + if (mb_len == 0) + break; + + new_len -= mb_len; + p += mb_len; + + if (new_len <= len) + break; + } + + return g_strconcat("...", p, NULL); +} + +GList *uri_list_extract_filenames(const gchar *uri_list) +{ + GList *result = NULL; + gchar *file; + +#if GLIB_CHECK_VERSION(2, 6, 0) + gchar **uris; + gint i; + + uris = g_uri_list_extract_uris(uri_list); + g_return_val_if_fail(uris != NULL, NULL); + + for (i = 0; uris[i] != NULL; i++) { + file = g_filename_from_uri(uris[i], NULL, NULL); + if (file) + result = g_list_append(result, file); + } + + g_strfreev(uris); + + return result; +#else + const gchar *p, *q; + + p = uri_list; + + while (p) { + if (*p != '#') { + while (g_ascii_isspace(*p)) p++; + if (!strncmp(p, "file:", 5)) { + p += 5; + while (*p == '/' && *(p + 1) == '/') p++; + q = p; + while (*q && *q != '\n' && *q != '\r') q++; + + if (q > p) { + q--; + while (q > p && g_ascii_isspace(*q)) + q--; + file = g_malloc(q - p + 2); + strncpy(file, p, q - p + 1); + file[q - p + 1] = '\0'; + decode_uri(file, file); + result = g_list_append(result, file); + } + } + } + p = strchr(p, '\n'); + if (p) p++; + } + + return result; +#endif +} + +#define HEX_TO_INT(val, hex) \ +{ \ + gchar c = hex; \ + \ + if ('0' <= c && c <= '9') { \ + val = c - '0'; \ + } else if ('a' <= c && c <= 'f') { \ + val = c - 'a' + 10; \ + } else if ('A' <= c && c <= 'F') { \ + val = c - 'A' + 10; \ + } else { \ + val = 0; \ + } \ +} + +#define INT_TO_HEX(hex, val) \ +{ \ + if ((val) < 10) \ + hex = '0' + (val); \ + else \ + hex = 'a' + (val) - 10; \ +} + +/* Converts two-digit hexadecimal to decimal. Used for unescaping escaped + * characters. + */ +static gint axtoi(const gchar *hex_str) +{ + gint hi, lo; + + HEX_TO_INT(hi, hex_str[0]); + HEX_TO_INT(lo, hex_str[1]); + + return (hi << 4) + lo; +} + +static void get_hex_str(gchar *out, guchar ch) +{ + gchar hex; + + INT_TO_HEX(hex, ch >> 4); + *out++ = hex; + INT_TO_HEX(hex, ch & 0x0f); + *out++ = hex; +} + +gboolean is_uri_string(const gchar *str) +{ + return (g_ascii_strncasecmp(str, "http://", 7) == 0 || + g_ascii_strncasecmp(str, "https://", 8) == 0 || + g_ascii_strncasecmp(str, "ftp://", 6) == 0 || + g_ascii_strncasecmp(str, "www.", 4) == 0); +} + +gchar *get_uri_path(const gchar *uri) +{ + if (g_ascii_strncasecmp(uri, "http://", 7) == 0) + return (gchar *)(uri + 7); + else if (g_ascii_strncasecmp(uri, "https://", 8) == 0) + return (gchar *)(uri + 8); + else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0) + return (gchar *)(uri + 6); + else + return (gchar *)uri; +} + +gint get_uri_len(const gchar *str) +{ + const gchar *p; + + if (is_uri_string(str)) { + for (p = str; *p != '\0'; p++) { + if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p)) + break; + } + return p - str; + } + + return 0; +} + +/* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by + * plusses, and escape characters are used) + * Note: decoded_uri and encoded_uri can point the same location + */ +void decode_uri(gchar *decoded_uri, const gchar *encoded_uri) +{ + gchar *dec = decoded_uri; + const gchar *enc = encoded_uri; + + while (*enc) { + if (*enc == '%') { + enc++; + if (g_ascii_isxdigit((guchar)enc[0]) && + g_ascii_isxdigit((guchar)enc[1])) { + *dec = axtoi(enc); + dec++; + enc += 2; + } + } else { + if (*enc == '+') + *dec = ' '; + else + *dec = *enc; + dec++; + enc++; + } + } + + *dec = '\0'; +} + +void decode_xdigit_encoded_str(gchar *decoded, const gchar *encoded) +{ + gchar *dec = decoded; + const gchar *enc = encoded; + + while (*enc) { + if (*enc == '%') { + enc++; + if (g_ascii_isxdigit((guchar)enc[0]) && + g_ascii_isxdigit((guchar)enc[1])) { + *dec++ = axtoi(enc); + enc += 2; + } + } else + *dec++ = *enc++; + } + + *dec = '\0'; +} + +gchar *encode_uri(const gchar *filename) +{ + gchar *uri; + + uri = g_filename_to_uri(filename, NULL, NULL); + if (!uri) + uri = g_strconcat("file://", filename, NULL); + + return uri; +} + +gchar *uriencode_for_filename(const gchar *filename) +{ + const gchar *p = filename; + gchar *enc, *outp; + + outp = enc = g_malloc(strlen(filename) * 3 + 1); + + for (p = filename; *p != '\0'; p++) { + if (strchr("\t\r\n\"'\\/:;*?<>|", *p)) { + *outp++ = '%'; + get_hex_str(outp, *p); + outp += 2; + } else + *outp++ = *p; + } + + *outp = '\0'; + return enc; +} + +gchar *uriencode_for_mailto(const gchar *mailto) +{ + const gchar *p = mailto; + gchar *enc, *outp; + + outp = enc = g_malloc(strlen(mailto) * 3 + 1); + + for (p = mailto; *p != '\0'; p++) { + if (*p == '+') { + *outp++ = '%'; + get_hex_str(outp, *p); + outp += 2; + } else + *outp++ = *p; + } + + *outp = '\0'; + return enc; +} + +gint scan_mailto_url(const gchar *mailto, gchar **to, gchar **cc, gchar **bcc, + gchar **subject, gchar **inreplyto, gchar **body) +{ + gchar *tmp_mailto; + gchar *p; + + Xstrdup_a(tmp_mailto, mailto, return -1); + + if (!strncmp(tmp_mailto, "mailto:", 7)) + tmp_mailto += 7; + + p = strchr(tmp_mailto, '?'); + if (p) { + *p = '\0'; + p++; + } + + if (to && !*to) { + *to = g_malloc(strlen(tmp_mailto) + 1); + decode_uri(*to, tmp_mailto); + } + + while (p) { + gchar *field, *value; + + field = p; + + p = strchr(p, '='); + if (!p) break; + *p = '\0'; + p++; + + value = p; + + p = strchr(p, '&'); + if (p) { + *p = '\0'; + p++; + } + + if (*value == '\0') continue; + + if (cc && !*cc && !g_ascii_strcasecmp(field, "cc")) { + *cc = g_malloc(strlen(value) + 1); + decode_uri(*cc, value); + } else if (bcc && !*bcc && !g_ascii_strcasecmp(field, "bcc")) { + *bcc = g_malloc(strlen(value) + 1); + decode_uri(*bcc, value); + } else if (subject && !*subject && + !g_ascii_strcasecmp(field, "subject")) { + *subject = g_malloc(strlen(value) + 1); + decode_uri(*subject, value); + } else if (inreplyto && !*inreplyto && + !g_ascii_strcasecmp(field, "in-reply-to")) { + *inreplyto = g_malloc(strlen(value) + 1); + decode_uri(*inreplyto, value); + } else if (body && !*body && + !g_ascii_strcasecmp(field, "body")) { + *body = g_malloc(strlen(value) + 1); + decode_uri(*body, value); + } + } + + return 0; +} + +static gchar *startup_dir = NULL; +static gchar *rc_dir = NULL; + +void set_startup_dir(void) +{ +#ifdef G_OS_WIN32 + if (!startup_dir) { + startup_dir = g_win32_get_package_installation_directory + (NULL, NULL); + if (startup_dir) { + if (g_chdir(startup_dir) < 0) { + FILE_OP_ERROR(startup_dir, "chdir"); + g_free(startup_dir); + startup_dir = g_get_current_dir(); + } + } else + startup_dir = g_get_current_dir(); + } +#elif defined(__APPLE__) + if (!startup_dir) { + const gchar *path; + NSAutoreleasePool *pool; + + pool = [[NSAutoreleasePool alloc] init]; + + path = [[[NSBundle mainBundle] bundlePath] UTF8String]; + startup_dir = g_strdup(path); + + [pool release]; + + if (!startup_dir) + startup_dir = g_get_current_dir(); + } +#else + if (!startup_dir) + startup_dir = g_get_current_dir(); +#endif +} + +void set_rc_dir(const gchar *dir) +{ + if (rc_dir) + g_free(rc_dir); + + if (dir) { + if (g_path_is_absolute(dir)) + rc_dir = g_strdup(dir); + else + rc_dir = g_strconcat(get_startup_dir(), + G_DIR_SEPARATOR_S, dir, NULL); + } else + rc_dir = NULL; +} + +const gchar *get_startup_dir(void) +{ + if (!startup_dir) + set_startup_dir(); + + return startup_dir; +} + +#ifdef G_OS_WIN32 +static gchar *get_win32_special_folder_path(gint nfolder) +{ + gchar *folder = NULL; + HRESULT hr; + + if (G_WIN32_HAVE_WIDECHAR_API()) { + wchar_t path[MAX_PATH + 1]; + hr = SHGetFolderPathW(NULL, nfolder, NULL, 0, path); + if (hr == S_OK) + folder = g_utf16_to_utf8(path, -1, NULL, NULL, NULL); + } else { + gchar path[MAX_PATH + 1]; + hr = SHGetFolderPathA(NULL, nfolder, NULL, 0, path); + if (hr == S_OK) + folder = g_locale_to_utf8(path, -1, NULL, NULL, NULL); + } + + return folder; +} +#endif + +const gchar *get_home_dir(void) +{ +#ifdef G_OS_WIN32 + static const gchar *home_dir = NULL; + + if (!home_dir) { + home_dir = g_get_home_dir(); + if (!home_dir) + home_dir = "C:\\Sylpheed"; + } + + return home_dir; +#else + return g_get_home_dir(); +#endif +} + +const gchar *get_document_dir(void) +{ +#ifdef G_OS_WIN32 + static const gchar *document_dir = NULL; + HRESULT hr; + + if (!document_dir) { + document_dir = get_win32_special_folder_path(CSIDL_PERSONAL); + if (!document_dir) + document_dir = get_home_dir(); + } + + return document_dir; +#elif defined(__APPLE__) + static const gchar *document_dir = NULL; + + if (!document_dir) { + document_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, + "Documents", NULL); + } + + return document_dir; +#else + return get_home_dir(); +#endif +} + +const gchar *get_rc_dir(void) +{ + if (!rc_dir) { +#ifdef G_OS_WIN32 + gchar *appdata; + + appdata = get_win32_special_folder_path(CSIDL_APPDATA); + if (appdata) + rc_dir = g_strconcat(appdata, G_DIR_SEPARATOR_S, + RC_DIR, NULL); + else + rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, + RC_DIR, NULL); + g_free(appdata); +#elif defined(__APPLE__) + rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, + "Library", G_DIR_SEPARATOR_S, + "Application Support", G_DIR_SEPARATOR_S, + RC_DIR, NULL); +#else + rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, + RC_DIR, NULL); +#endif + } + + return rc_dir; +} + +const gchar *get_old_rc_dir(void) +{ + static gchar *old_rc_dir = NULL; + + if (!old_rc_dir) + old_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, + OLD_RC_DIR, NULL); + + return old_rc_dir; +} + +const gchar *get_mail_base_dir(void) +{ +#if defined(G_OS_WIN32) || defined(__APPLE__) + static gchar *mail_base_dir = NULL; + + if (!mail_base_dir) + mail_base_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + "Mailboxes", NULL); + + return mail_base_dir; +#else + return get_home_dir(); +#endif +} + +const gchar *get_news_cache_dir(void) +{ + static gchar *news_cache_dir = NULL; + + if (!news_cache_dir) + news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + NEWS_CACHE_DIR, NULL); + + return news_cache_dir; +} + +const gchar *get_imap_cache_dir(void) +{ + static gchar *imap_cache_dir = NULL; + + if (!imap_cache_dir) + imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + IMAP_CACHE_DIR, NULL); + + return imap_cache_dir; +} + +const gchar *get_mime_tmp_dir(void) +{ + static gchar *mime_tmp_dir = NULL; + + if (!mime_tmp_dir) + mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + MIME_TMP_DIR, NULL); + + return mime_tmp_dir; +} + +const gchar *get_template_dir(void) +{ + static gchar *template_dir = NULL; + + if (!template_dir) + template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + TEMPLATE_DIR, NULL); + + return template_dir; +} + +const gchar *get_tmp_dir(void) +{ + static gchar *tmp_dir = NULL; + + if (!tmp_dir) + tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + TMP_DIR, NULL); + + return tmp_dir; +} + +gchar *get_tmp_file(void) +{ + gchar *tmp_file; + static guint32 id = 0; + + tmp_file = g_strdup_printf("%s%ctmpfile.%08x", + get_tmp_dir(), G_DIR_SEPARATOR, id++); + + return tmp_file; +} + +const gchar *get_domain_name(void) +{ + static gchar *domain_name = NULL; + + if (!domain_name) { + gchar buf[128] = ""; + struct hostent *hp; + + if (gethostname(buf, sizeof(buf)) < 0) { + perror("gethostname"); + domain_name = "unknown"; + } else { + buf[sizeof(buf) - 1] = '\0'; + if ((hp = my_gethostbyname(buf)) == NULL) { + perror("gethostbyname"); + domain_name = g_strdup(buf); + } else { + domain_name = g_strdup(hp->h_name); + } + } + + debug_print("domain name = %s\n", domain_name); + if (is_next_nonascii(domain_name)) { + g_warning("invalid domain name: %s\n", domain_name); + g_free(domain_name); + domain_name = "unknown"; + } + } + + return domain_name; +} + +off_t get_file_size(const gchar *file) +{ + GStatBuf s; + + if (g_stat(file, &s) < 0) { + FILE_OP_ERROR(file, "stat"); + return -1; + } + + return s.st_size; +} + +off_t get_file_size_as_crlf(const gchar *file) +{ + FILE *fp; + off_t size = 0; + gchar buf[BUFFSIZE]; + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + size += strlen(buf) + 2; + } + + if (ferror(fp)) { + FILE_OP_ERROR(file, "fgets"); + size = -1; + } + + fclose(fp); + + return size; +} + +off_t get_left_file_size(FILE *fp) +{ + glong pos; + glong end; + off_t size; + + if ((pos = ftell(fp)) < 0) { + perror("ftell"); + return -1; + } + if (fseek(fp, 0L, SEEK_END) < 0) { + perror("fseek"); + return -1; + } + if ((end = ftell(fp)) < 0) { + perror("fseek"); + return -1; + } + size = end - pos; + if (fseek(fp, pos, SEEK_SET) < 0) { + perror("fseek"); + return -1; + } + + return size; +} + +gint get_last_empty_line_size(FILE *fp, off_t size) +{ + glong pos; + gint lsize = 0; + gchar buf[4]; + size_t nread; + + if (size < 4) + return -1; + + if ((pos = ftell(fp)) < 0) { + perror("ftell"); + return -1; + } + if (fseek(fp, size - 4, SEEK_CUR) < 0) { + perror("fseek"); + return -1; + } + + /* read last 4 bytes */ + nread = fread(buf, sizeof(buf), 1, fp); + if (nread != 1) { + perror("fread"); + return -1; + } + /* g_print("last 4 bytes: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]); */ + if (buf[3] == '\n') { + if (buf[2] == '\n') + lsize = 1; + else if (buf[2] == '\r') { + if (buf[1] == '\n') + lsize = 2; + } + } + + if (fseek(fp, pos, SEEK_SET) < 0) { + perror("fseek"); + return -1; + } + + return lsize; +} + +gboolean file_exist(const gchar *file, gboolean allow_fifo) +{ + if (file == NULL) + return FALSE; + + if (allow_fifo) { + GStatBuf s; + + if (g_stat(file, &s) < 0) { + if (ENOENT != errno) FILE_OP_ERROR(file, "stat"); + return FALSE; + } + if (S_ISREG(s.st_mode) || S_ISFIFO(s.st_mode)) + return TRUE; + } else { + return g_file_test(file, G_FILE_TEST_IS_REGULAR); + } + + return FALSE; +} + +gboolean is_dir_exist(const gchar *dir) +{ + if (dir == NULL) + return FALSE; + + return g_file_test(dir, G_FILE_TEST_IS_DIR); +} + +gboolean is_file_entry_exist(const gchar *file) +{ + if (file == NULL) + return FALSE; + + return g_file_test(file, G_FILE_TEST_EXISTS); +} + +gboolean dirent_is_regular_file(struct dirent *d) +{ +#ifdef HAVE_DIRENT_D_TYPE + if (d->d_type == DT_REG) + return TRUE; + else if (d->d_type != DT_UNKNOWN) + return FALSE; +#endif + + return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR); +} + +gboolean dirent_is_directory(struct dirent *d) +{ +#ifdef HAVE_DIRENT_D_TYPE + if (d->d_type == DT_DIR) + return TRUE; + else if (d->d_type != DT_UNKNOWN) + return FALSE; +#endif + + return g_file_test(d->d_name, G_FILE_TEST_IS_DIR); +} + +gint change_dir(const gchar *dir) +{ + gchar *prevdir = NULL; + + if (debug_mode) + prevdir = g_get_current_dir(); + + if (g_chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + if (debug_mode) g_free(prevdir); + return -1; + } else if (debug_mode) { + gchar *cwd; + + cwd = g_get_current_dir(); + if (strcmp(prevdir, cwd) != 0) + g_print("current dir: %s\n", cwd); + g_free(cwd); + g_free(prevdir); + } + + return 0; +} + +gint make_dir(const gchar *dir) +{ + if (g_mkdir(dir, S_IRWXU) < 0) { + FILE_OP_ERROR(dir, "mkdir"); + return -1; + } + if (g_chmod(dir, S_IRWXU) < 0) + FILE_OP_ERROR(dir, "chmod"); + + return 0; +} + +gint make_dir_hier(const gchar *dir) +{ + gchar *parent_dir; + const gchar *p; + + for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) { + parent_dir = g_strndup(dir, p - dir); + if (*parent_dir != '\0') { + if (!is_dir_exist(parent_dir)) { + if (make_dir(parent_dir) < 0) { + g_free(parent_dir); + return -1; + } + } + } + g_free(parent_dir); + } + + if (!is_dir_exist(dir)) { + if (make_dir(dir) < 0) + return -1; + } + + return 0; +} + +gint remove_all_files(const gchar *dir) +{ + GDir *dp; + const gchar *dir_name; + gchar *prev_dir; + + prev_dir = g_get_current_dir(); + + if (g_chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + g_free(prev_dir); + return -1; + } + + if ((dp = g_dir_open(".", 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", dir); + g_free(prev_dir); + return -1; + } + + while ((dir_name = g_dir_read_name(dp)) != NULL) { + if (g_unlink(dir_name) < 0) + FILE_OP_ERROR(dir_name, "unlink"); + } + + g_dir_close(dp); + + if (g_chdir(prev_dir) < 0) { + FILE_OP_ERROR(prev_dir, "chdir"); + g_free(prev_dir); + return -1; + } + + g_free(prev_dir); + + return 0; +} + +gint remove_numbered_files(const gchar *dir, guint first, guint last) +{ + GDir *dp; + const gchar *dir_name; + gchar *prev_dir; + guint file_no; + + prev_dir = g_get_current_dir(); + + if (g_chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + g_free(prev_dir); + return -1; + } + + if ((dp = g_dir_open(".", 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", dir); + g_free(prev_dir); + return -1; + } + + while ((dir_name = g_dir_read_name(dp)) != NULL) { + file_no = to_unumber(dir_name); + if (file_no > 0 && first <= file_no && file_no <= last) { + if (is_dir_exist(dir_name)) + continue; + if (g_unlink(dir_name) < 0) + FILE_OP_ERROR(dir_name, "unlink"); + } + } + + g_dir_close(dp); + + if (g_chdir(prev_dir) < 0) { + FILE_OP_ERROR(prev_dir, "chdir"); + g_free(prev_dir); + return -1; + } + + g_free(prev_dir); + + return 0; +} + +gint remove_all_numbered_files(const gchar *dir) +{ + return remove_numbered_files(dir, 0, UINT_MAX); +} + +gint remove_expired_files(const gchar *dir, guint hours) +{ + GDir *dp; + const gchar *dir_name; + GStatBuf s; + gchar *prev_dir; + guint file_no; + time_t mtime, now, expire_time; + + prev_dir = g_get_current_dir(); + + if (g_chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + g_free(prev_dir); + return -1; + } + + if ((dp = g_dir_open(".", 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", dir); + g_free(prev_dir); + return -1; + } + + now = time(NULL); + expire_time = hours * 60 * 60; + + while ((dir_name = g_dir_read_name(dp)) != NULL) { + file_no = to_unumber(dir_name); + if (file_no > 0) { + if (g_stat(dir_name, &s) < 0) { + FILE_OP_ERROR(dir_name, "stat"); + continue; + } + if (S_ISDIR(s.st_mode)) + continue; + mtime = MAX(s.st_mtime, s.st_atime); + if (now - mtime > expire_time) { + if (g_unlink(dir_name) < 0) + FILE_OP_ERROR(dir_name, "unlink"); + } + } + } + + g_dir_close(dp); + + if (g_chdir(prev_dir) < 0) { + FILE_OP_ERROR(prev_dir, "chdir"); + g_free(prev_dir); + return -1; + } + + g_free(prev_dir); + + return 0; +} + +static gint remove_dir_recursive_real(const gchar *dir) +{ + GStatBuf s; + GDir *dp; + const gchar *dir_name; + gchar *prev_dir; + + if (g_stat(dir, &s) < 0) { + FILE_OP_ERROR(dir, "stat"); + if (ENOENT == errno) return 0; + return -1; + } + + if (!S_ISDIR(s.st_mode)) { + if (g_unlink(dir) < 0) { + FILE_OP_ERROR(dir, "unlink"); + return -1; + } + + return 0; + } + + prev_dir = g_get_current_dir(); + /* g_print("prev_dir = %s\n", prev_dir); */ + + if (g_chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + g_free(prev_dir); + return -1; + } + + if ((dp = g_dir_open(".", 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", dir); + g_chdir(prev_dir); + g_free(prev_dir); + return -1; + } + + /* remove all files in the directory */ + while ((dir_name = g_dir_read_name(dp)) != NULL) { + /* g_print("removing %s\n", dir_name); */ + + if (is_dir_exist(dir_name)) { + if (remove_dir_recursive_real(dir_name) < 0) { + g_warning("can't remove directory\n"); + return -1; + } + } else { + if (g_unlink(dir_name) < 0) + FILE_OP_ERROR(dir_name, "unlink"); + } + } + + g_dir_close(dp); + + if (g_chdir(prev_dir) < 0) { + FILE_OP_ERROR(prev_dir, "chdir"); + g_free(prev_dir); + return -1; + } + + g_free(prev_dir); + + if (g_rmdir(dir) < 0) { + if (ENOTDIR == errno) { + if (g_unlink(dir) < 0) { + FILE_OP_ERROR(dir, "unlink"); + return -1; + } + } else { + FILE_OP_ERROR(dir, "rmdir"); + return -1; + } + } + + return 0; +} + +gint remove_dir_recursive(const gchar *dir) +{ + gchar *cur_dir; + gint ret; + + debug_print("remove_dir_recursive: %s\n", dir); + + cur_dir = g_get_current_dir(); + + if (g_chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + ret = -1; + goto leave; + } + if (g_chdir("..") < 0) { + FILE_OP_ERROR(dir, "chdir"); + ret = -1; + goto leave; + } + + ret = remove_dir_recursive_real(dir); + +leave: + if (is_dir_exist(cur_dir)) { + if (g_chdir(cur_dir) < 0) { + FILE_OP_ERROR(cur_dir, "chdir"); + } + } + + g_free(cur_dir); + + return ret; +} + +gint rename_force(const gchar *oldpath, const gchar *newpath) +{ +#if !defined(G_OS_UNIX) && !GLIB_CHECK_VERSION(2, 9, 1) + if (!is_file_entry_exist(oldpath)) { + errno = ENOENT; + return -1; + } + if (is_file_exist(newpath)) { + if (g_unlink(newpath) < 0) + FILE_OP_ERROR(newpath, "unlink"); + } +#endif + return g_rename(oldpath, newpath); +} + +gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup) +{ +#ifdef G_OS_WIN32 + wchar_t *wsrc; + wchar_t *wdest; + gchar *dest_bak = NULL; + gboolean err = FALSE; + + wsrc = g_utf8_to_utf16(src, -1, NULL, NULL, NULL); + if (wsrc == NULL) { + return -1; + } + wdest = g_utf8_to_utf16(dest, -1, NULL, NULL, NULL); + if (wdest == NULL) { + g_free(wsrc); + return -1; + } + + if (keep_backup == FALSE) { + if (CopyFileW(wsrc, wdest, FALSE) == 0) + err = TRUE; + g_free(wdest); + g_free(wsrc); + return err ? -1 : 0; + } + + if (is_file_exist(dest)) { + dest_bak = g_strconcat(dest, ".bak", NULL); + if (rename_force(dest, dest_bak) < 0) { + FILE_OP_ERROR(dest, "rename"); + g_free(dest_bak); + g_free(wdest); + g_free(wsrc); + return -1; + } + } + + if (CopyFileW(wsrc, wdest, FALSE) == 0) + err = TRUE; + + g_free(wdest); + g_free(wsrc); +#else + gint srcfd, destfd; + gint n_read; + gchar buf[BUFFSIZE]; + gchar *dest_bak = NULL; + gboolean err = FALSE; + + if ((srcfd = g_open(src, O_RDONLY, 0600)) < 0) { + FILE_OP_ERROR(src, "open"); + return -1; + } + if (is_file_exist(dest)) { + dest_bak = g_strconcat(dest, ".bak", NULL); + if (rename_force(dest, dest_bak) < 0) { + FILE_OP_ERROR(dest, "rename"); + close(srcfd); + g_free(dest_bak); + return -1; + } + } + + if ((destfd = g_open(dest, O_WRONLY | O_CREAT, 0600)) < 0) { + FILE_OP_ERROR(dest, "open"); + close(srcfd); + if (dest_bak) { + if (rename_force(dest_bak, dest) < 0) + FILE_OP_ERROR(dest_bak, "rename"); + g_free(dest_bak); + } + return -1; + } + + while ((n_read = read(srcfd, buf, sizeof(buf))) > 0) { + gchar *p = buf; + const gchar *endp = buf + n_read; + gint n_write; + + while (p < endp) { + if ((n_write = write(destfd, p, endp - p)) < 0) { + g_warning(_("writing to %s failed.\n"), dest); + close(destfd); + close(srcfd); + g_unlink(dest); + if (dest_bak) { + if (rename_force(dest_bak, dest) < 0) + FILE_OP_ERROR(dest_bak, "rename"); + g_free(dest_bak); + } + return -1; + } + p += n_write; + } + } + + if (close(destfd) < 0) { + FILE_OP_ERROR(dest, "close"); + err = TRUE; + } + close(srcfd); +#endif + + if (err) { + g_unlink(dest); + if (dest_bak) { + if (rename_force(dest_bak, dest) < 0) + FILE_OP_ERROR(dest_bak, "rename"); + g_free(dest_bak); + } + return -1; + } + + if (keep_backup == FALSE && dest_bak) + g_unlink(dest_bak); + + g_free(dest_bak); + + return 0; +} + +gint copy_dir(const gchar *src, const gchar *dest) +{ + GDir *dir; + const gchar *dir_name; + gchar *src_file; + gchar *dest_file; + + if ((dir = g_dir_open(src, 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", src); + return -1; + } + + if (make_dir_hier(dest) < 0) { + g_dir_close(dir); + return -1; + } + + while ((dir_name = g_dir_read_name(dir)) != NULL) { + src_file = g_strconcat(src, G_DIR_SEPARATOR_S, dir_name, NULL); + dest_file = g_strconcat(dest, G_DIR_SEPARATOR_S, dir_name, + NULL); + if (is_file_exist(src_file)) + copy_file(src_file, dest_file, FALSE); + g_free(dest_file); + g_free(src_file); + } + + g_dir_close(dir); + + return 0; +} + +gint move_file(const gchar *src, const gchar *dest, gboolean overwrite) +{ + if (overwrite == FALSE && is_file_entry_exist(dest)) { + g_warning("move_file(): file %s already exists.", dest); + return -1; + } + + if (rename_force(src, dest) == 0) return 0; + + if (EXDEV != errno) { + FILE_OP_ERROR(src, "rename"); + return -1; + } + + if (copy_file(src, dest, FALSE) < 0) return -1; + + g_unlink(src); + + return 0; +} + +gint append_file_part(FILE *fp, off_t offset, size_t length, FILE *dest_fp) +{ + gint n_read; + gint bytes_left, to_read; + gchar buf[BUFSIZ]; + + g_return_val_if_fail(fp != NULL, -1); + g_return_val_if_fail(dest_fp != NULL, -1); + + if (fseek(fp, offset, SEEK_SET) < 0) { + perror("fseek"); + return -1; + } + + bytes_left = length; + to_read = MIN(bytes_left, sizeof(buf)); + + while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) { + if (n_read < to_read && ferror(fp)) + break; + if (fwrite(buf, n_read, 1, dest_fp) < 1) { + g_warning("append_file_part: writing to file failed.\n"); + return -1; + } + bytes_left -= n_read; + if (bytes_left == 0) + break; + to_read = MIN(bytes_left, sizeof(buf)); + } + + if (ferror(fp)) { + perror("fread"); + return -1; + } + if (fflush(dest_fp) == EOF) { + FILE_OP_ERROR("append_file_part", "fflush"); + return -1; + } + + return 0; +} + +gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest) +{ + FILE *dest_fp; + + if ((dest_fp = g_fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning("can't change file mode\n"); + } + + if (append_file_part(fp, offset, length, dest_fp) < 0) { + g_warning("writing to %s failed.\n", dest); + fclose(dest_fp); + g_unlink(dest); + return -1; + } + + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + g_unlink(dest); + return -1; + } + + return 0; +} + +gint copy_file_stream(FILE *fp, FILE *dest_fp) +{ + gint n_read; + gchar buf[BUFFSIZE]; + + g_return_val_if_fail(fp != NULL, -1); + g_return_val_if_fail(dest_fp != NULL, -1); + + while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) { + if (n_read < sizeof(buf) && ferror(fp)) + break; + if (fwrite(buf, n_read, 1, dest_fp) < 1) { + g_warning("copy_file_stream: writing to file failed.\n"); + return -1; + } + } + + if (ferror(fp)) { + perror("fread"); + return -1; + } + if (fflush(dest_fp) == EOF) { + FILE_OP_ERROR("copy_file_stream", "fflush"); + return -1; + } + + return 0; +} + +/* convert line endings into CRLF. If the last line doesn't end with + * linebreak, add it. + */ +gchar *canonicalize_str(const gchar *str) +{ + const gchar *p; + guint new_len = 0; + gchar *out, *outp; + + for (p = str; *p != '\0'; ++p) { + if (*p != '\r') { + ++new_len; + if (*p == '\n') + ++new_len; + } + } + if (p == str || *(p - 1) != '\n') + new_len += 2; + + out = outp = g_malloc(new_len + 1); + for (p = str; *p != '\0'; ++p) { + if (*p != '\r') { + if (*p == '\n') + *outp++ = '\r'; + *outp++ = *p; + } + } + if (p == str || *(p - 1) != '\n') { + *outp++ = '\r'; + *outp++ = '\n'; + } + *outp = '\0'; + + return out; +} + +gint canonicalize_file(const gchar *src, const gchar *dest) +{ + FILE *src_fp, *dest_fp; + gchar buf[BUFFSIZE]; + gint len; + gboolean err = FALSE; + gboolean last_linebreak = FALSE; + + if ((src_fp = g_fopen(src, "rb")) == NULL) { + FILE_OP_ERROR(src, "fopen"); + return -1; + } + + if ((dest_fp = g_fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + fclose(src_fp); + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning("can't change file mode\n"); + } + + while (fgets(buf, sizeof(buf), src_fp) != NULL) { + gint r = 0; + + len = strlen(buf); + if (len == 0) break; + last_linebreak = FALSE; + + if (buf[len - 1] != '\n') { + last_linebreak = TRUE; + r = fputs(buf, dest_fp); + } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') { + r = fputs(buf, dest_fp); + } else { + if (len > 1) { + r = fwrite(buf, len - 1, 1, dest_fp); + if (r != 1) + r = EOF; + } + if (r != EOF) + r = fputs("\r\n", dest_fp); + } + + if (r == EOF) { + g_warning("writing to %s failed.\n", dest); + fclose(dest_fp); + fclose(src_fp); + g_unlink(dest); + return -1; + } + } + + if (last_linebreak == TRUE) { + if (fputs("\r\n", dest_fp) == EOF) + err = TRUE; + } + + if (ferror(src_fp)) { + FILE_OP_ERROR(src, "fgets"); + err = TRUE; + } + fclose(src_fp); + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + err = TRUE; + } + + if (err) { + g_unlink(dest); + return -1; + } + + return 0; +} + +gint canonicalize_file_replace(const gchar *file) +{ + gchar *tmp_file; + + tmp_file = get_tmp_file(); + + if (canonicalize_file(file, tmp_file) < 0) { + g_free(tmp_file); + return -1; + } + + if (move_file(tmp_file, file, TRUE) < 0) { + g_warning("can't replace %s .\n", file); + g_unlink(tmp_file); + g_free(tmp_file); + return -1; + } + + g_free(tmp_file); + return 0; +} + +FILE *canonicalize_file_stream(FILE *src_fp, gint *length) +{ + FILE *dest_fp; + gchar buf[BUFFSIZE]; + gint len; + gint length_ = 0; + gboolean err = FALSE; + gboolean last_linebreak = FALSE; + + if ((dest_fp = my_tmpfile()) == NULL) + return NULL; + + while (fgets(buf, sizeof(buf), src_fp) != NULL) { + gint r = 0; + + len = strlen(buf); + if (len == 0) break; + last_linebreak = FALSE; + + if (buf[len - 1] != '\n') { + last_linebreak = TRUE; + r = fputs(buf, dest_fp); + length_ += len; + } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') { + r = fputs(buf, dest_fp); + length_ += len; + } else { + if (len > 1) { + r = fwrite(buf, len - 1, 1, dest_fp); + if (r != 1) + r = EOF; + else + length_ += len - 1; + } + if (r != EOF) { + r = fputs("\r\n", dest_fp); + length_ += 2; + } + } + + if (r == EOF) { + g_warning("writing to temporary file failed.\n"); + fclose(dest_fp); + return NULL; + } + } + + if (last_linebreak == TRUE) { + if (fputs("\r\n", dest_fp) == EOF) + err = TRUE; + else + length_ += 2; + } + + if (ferror(src_fp)) { + FILE_OP_ERROR("canonicalize_file_stream", "fgets"); + err = TRUE; + } + if (fflush(dest_fp) == EOF) { + FILE_OP_ERROR("canonicalize_file_stream", "fflush"); + err = TRUE; + } + + if (err) { + fclose(dest_fp); + return NULL; + } + + if (length) + *length = length_; + + rewind(dest_fp); + return dest_fp; +} + +gint uncanonicalize_file(const gchar *src, const gchar *dest) +{ + FILE *src_fp, *dest_fp; + gchar buf[BUFFSIZE]; + gboolean err = FALSE; + + if ((src_fp = g_fopen(src, "rb")) == NULL) { + FILE_OP_ERROR(src, "fopen"); + return -1; + } + + if ((dest_fp = g_fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + fclose(src_fp); + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning("can't change file mode\n"); + } + + while (fgets(buf, sizeof(buf), src_fp) != NULL) { + strcrchomp(buf); + if (fputs(buf, dest_fp) == EOF) { + g_warning("writing to %s failed.\n", dest); + fclose(dest_fp); + fclose(src_fp); + g_unlink(dest); + return -1; + } + } + + if (ferror(src_fp)) { + FILE_OP_ERROR(src, "fgets"); + err = TRUE; + } + fclose(src_fp); + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + err = TRUE; + } + + if (err) { + g_unlink(dest); + return -1; + } + + return 0; +} + +gint uncanonicalize_file_replace(const gchar *file) +{ + gchar *tmp_file; + + tmp_file = get_tmp_file(); + + if (uncanonicalize_file(file, tmp_file) < 0) { + g_free(tmp_file); + return -1; + } + + if (move_file(tmp_file, file, TRUE) < 0) { + g_warning("can't replace %s .\n", file); + g_unlink(tmp_file); + g_free(tmp_file); + return -1; + } + + g_free(tmp_file); + return 0; +} + +gchar *normalize_newlines(const gchar *str) +{ + const gchar *p = str; + gchar *out, *outp; + + out = outp = g_malloc(strlen(str) + 1); + for (p = str; *p != '\0'; ++p) { + if (*p == '\r') { + if (*(p + 1) != '\n') + *outp++ = '\n'; + } else + *outp++ = *p; + } + + *outp = '\0'; + + return out; +} + +gchar *strchomp_all(const gchar *str) +{ + const gchar *p = str; + const gchar *newline, *last; + gchar *out, *outp; + + out = outp = g_malloc(strlen(str) + 1); + while (*p != '\0') { + newline = strchr(p, '\n'); + if (newline) { + for (last = newline; + p < last && g_ascii_isspace(*(last - 1)); --last) + ; + strncpy(outp, p, last - p); + outp += last - p; + + if (p < newline && *(newline - 1) == '\r') { + strncpy(outp, newline - 1, 2); + outp += 2; + } else { + *outp++ = *newline; + } + + p = newline + 1; + } else { + for (last = p + strlen(p); + p < last && g_ascii_isspace(*(last - 1)); --last) + ; + strncpy(outp, p, last - p); + outp += last - p; + break; + } + } + + *outp = '\0'; + + return out; +} + +FILE *get_outgoing_rfc2822_file(FILE *fp) +{ + gchar buf[BUFFSIZE]; + FILE *outfp; + + outfp = my_tmpfile(); + if (!outfp) { + FILE_OP_ERROR("get_outgoing_rfc2822_file", "my_tmpfile"); + return NULL; + } + + /* output header part */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) { + gint next; + + for (;;) { + next = fgetc(fp); + if (next == EOF) + break; + else if (next != ' ' && next != '\t') { + ungetc(next, fp); + break; + } + if (fgets(buf, sizeof(buf), fp) == NULL) + break; + } + } else { + if (fputs(buf, outfp) == EOF) + goto file_error; + if (fputs("\r\n", outfp) == EOF) + goto file_error; + if (buf[0] == '\0') + break; + } + } + + /* output body part */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + if (buf[0] == '.') { + if (fputc('.', outfp) == EOF) + goto file_error; + } + if (fputs(buf, outfp) == EOF) + goto file_error; + if (fputs("\r\n", outfp) == EOF) + goto file_error; + } + + if (fflush(outfp) == EOF) { + FILE_OP_ERROR("get_outgoing_rfc2822_file", "fflush"); + goto file_error; + } + + rewind(outfp); + return outfp; + +file_error: + g_warning("get_outgoing_rfc2822_file(): writing to temporary file failed.\n"); + fclose(outfp); + return NULL; +} + +gchar *get_outgoing_rfc2822_str(FILE *fp) +{ + gchar buf[BUFFSIZE]; + GString *str; + gchar *ret; + + str = g_string_new(NULL); + + /* output header part */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) { + gint next; + + for (;;) { + next = fgetc(fp); + if (next == EOF) + break; + else if (next != ' ' && next != '\t') { + ungetc(next, fp); + break; + } + if (fgets(buf, sizeof(buf), fp) == NULL) + break; + } +#if 0 + } else if (!g_ascii_strncasecmp(buf, "Date:", 5)) { + get_rfc822_date(buf, sizeof(buf)); + g_string_append_printf(str, "Date: %s\r\n", buf); +#endif + } else { + g_string_append(str, buf); + g_string_append(str, "\r\n"); + if (buf[0] == '\0') + break; + } + } + + /* output body part */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + if (buf[0] == '.') + g_string_append_c(str, '.'); + g_string_append(str, buf); + g_string_append(str, "\r\n"); + } + + ret = str->str; + g_string_free(str, FALSE); + + return ret; +} + +/* + * Create a new boundary in a way that it is very unlikely that this + * will occur in the following text. It would be easy to ensure + * uniqueness if everything is either quoted-printable or base64 + * encoded (note that conversion is allowed), but because MIME bodies + * may be nested, it may happen that the same boundary has already + * been used. We avoid scanning the message for conflicts and hope the + * best. + * + * boundary := 0*69 bcharsnospace + * bchars := bcharsnospace / " " + * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / + * "+" / "_" / "," / "-" / "." / + * "/" / ":" / "=" / "?" + * + * some special characters removed because of buggy MTAs + */ + +gchar *generate_mime_boundary(const gchar *prefix) +{ + static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "1234567890+_./="; + gchar buf_uniq[17]; + gchar buf_date[64]; + gint i; + + for (i = 0; i < sizeof(buf_uniq) - 1; i++) + buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)]; + buf_uniq[i] = '\0'; + + get_rfc822_date(buf_date, sizeof(buf_date)); + subst_chars(buf_date, " ,:", '_'); + + return g_strdup_printf("%s=_%s_%s", prefix ? prefix : "Multipart", + buf_date, buf_uniq); +} + +gint change_file_mode_rw(FILE *fp, const gchar *file) +{ +#ifdef G_OS_WIN32 + DWORD attr; + BOOL retval; + + if (G_WIN32_HAVE_WIDECHAR_API()) { + wchar_t *wpath; + + wpath = g_utf8_to_utf16(file, -1, NULL, NULL, NULL); + if (wpath == NULL) + return -1; + + attr = GetFileAttributesW(wpath); + retval = SetFileAttributesW + (wpath, attr & ~(FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN)); + + g_free(wpath); + } else { + gchar *cp_path; + + cp_path = g_locale_from_utf8(file, -1, NULL, NULL, NULL); + if (cp_path == NULL) + return -1; + + attr = GetFileAttributesA(cp_path); + retval = SetFileAttributesA + (cp_path, attr & ~(FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN)); + + g_free(cp_path); + } + + if (retval) + return 0; + else + return -1; +#else +#if HAVE_FCHMOD + if (fp) + return fchmod(fileno(fp), S_IRUSR|S_IWUSR); + else +#endif + return g_chmod(file, S_IRUSR|S_IWUSR); +#endif +} + +#ifdef G_OS_WIN32 +gchar *_s_tempnam(const gchar *dir, const gchar *prefix) +{ + if (G_WIN32_HAVE_WIDECHAR_API()) { + wchar_t *wpath; + wchar_t *wprefix; + wchar_t *wname; + gint save_errno; + gchar *name; + + wpath = g_utf8_to_utf16(dir, -1, NULL, NULL, NULL); + if (wpath == NULL) { + errno = EINVAL; + return NULL; + } + wprefix = g_utf8_to_utf16(prefix, -1, NULL, NULL, NULL); + if (wprefix == NULL) { + errno = EINVAL; + g_free(wpath); + return NULL; + } + + wname = _wtempnam(wpath, wprefix); + save_errno = errno; + + name = g_utf16_to_utf8(wname, -1, NULL, NULL, NULL); + if (name == NULL) { + save_errno = EINVAL; + } + + free(wname); /* must be freed by same MSVCRT used for + _wtempnam() */ + g_free(wprefix); + g_free(wpath); + + errno = save_errno; + return name; + } else { + gchar *cp_path; + gchar *cp_prefix; + gchar *cp_name; + gint save_errno; + gchar *name; + + cp_path = g_locale_from_utf8(dir, -1, NULL, NULL, NULL); + if (cp_path == NULL) { + errno = EINVAL; + return NULL; + } + + cp_prefix = g_locale_from_utf8(prefix, -1, NULL, NULL, NULL); + if (cp_prefix == NULL) { + errno = EINVAL; + g_free(cp_path); + return NULL; + } + + cp_name = _tempnam(cp_path, cp_prefix); + save_errno = errno; + + name = g_locale_to_utf8(cp_name, -1, NULL, NULL, NULL); + if (name == NULL) { + save_errno = EINVAL; + } + + free(cp_name); + g_free(cp_prefix); + g_free(cp_path); + + errno = save_errno; + return name; + } +} +#endif + +FILE *my_tmpfile(void) +{ +#ifdef G_OS_WIN32 + const gchar *tmpdir; + gchar *fname; + gint fd; + FILE *fp; + + tmpdir = get_tmp_dir(); + fname = _s_tempnam(tmpdir, "sylph"); + if (!fname) + return NULL; + + fd = g_open(fname, O_RDWR | O_CREAT | O_EXCL | + _O_TEMPORARY | _O_SHORT_LIVED | _O_BINARY, 0600); + if (fd < 0) { + g_free(fname); + return NULL; + } + + fp = fdopen(fd, "w+b"); + if (!fp) { + perror("fdopen"); + close(fd); + } + + g_free(fname); + + return fp; +#else + const gchar suffix[] = ".XXXXXX"; + const gchar *tmpdir; + guint tmplen; + const gchar *progname; + guint proglen; + gchar *fname; + gint fd; + FILE *fp; + + tmpdir = get_tmp_dir(); + tmplen = strlen(tmpdir); + progname = g_get_prgname(); + if (!progname) + progname = "sylph"; + proglen = strlen(progname); + fname = g_malloc(tmplen + 1 + proglen + sizeof(suffix)); + + memcpy(fname, tmpdir, tmplen); + fname[tmplen] = G_DIR_SEPARATOR; + memcpy(fname + tmplen + 1, progname, proglen); + memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix)); + + fd = g_mkstemp(fname); + if (fd < 0) { + g_free(fname); + return tmpfile(); + } + + g_unlink(fname); + + fp = fdopen(fd, "w+b"); + if (!fp) { + perror("fdopen"); + close(fd); + } + + g_free(fname); + + return fp; +#endif +} + +FILE *str_open_as_stream(const gchar *str) +{ + FILE *fp; + size_t len; + + g_return_val_if_fail(str != NULL, NULL); + + fp = my_tmpfile(); + if (!fp) { + FILE_OP_ERROR("str_open_as_stream", "my_tmpfile"); + return NULL; + } + + len = strlen(str); + if (len == 0) return fp; + + if (fwrite(str, len, 1, fp) != 1) { + FILE_OP_ERROR("str_open_as_stream", "fwrite"); + fclose(fp); + return NULL; + } + if (fflush(fp) == EOF) { + FILE_OP_ERROR("str_open_as_stream", "fflush"); + fclose(fp); + return NULL; + } + + rewind(fp); + return fp; +} + +gint str_write_to_file(const gchar *str, const gchar *file) +{ + FILE *fp; + size_t len; + + g_return_val_if_fail(str != NULL, -1); + g_return_val_if_fail(file != NULL, -1); + + if ((fp = g_fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + len = strlen(str); + if (len == 0) { + fclose(fp); + return 0; + } + + if (fwrite(str, len, 1, fp) != 1) { + FILE_OP_ERROR(file, "fwrite"); + fclose(fp); + g_unlink(file); + return -1; + } + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(file, "fclose"); + g_unlink(file); + return -1; + } + + return 0; +} + +gchar *file_read_to_str(const gchar *file) +{ + FILE *fp; + gchar *str; + + g_return_val_if_fail(file != NULL, NULL); + + if ((fp = g_fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return NULL; + } + + str = file_read_stream_to_str(fp); + + fclose(fp); + + return str; +} + +gchar *file_read_stream_to_str(FILE *fp) +{ + GByteArray *array; + guchar buf[BUFSIZ]; + gint n_read; + gchar *str; + + g_return_val_if_fail(fp != NULL, NULL); + + array = g_byte_array_new(); + + while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) { + if (n_read < sizeof(buf) && ferror(fp)) + break; + g_byte_array_append(array, buf, n_read); + } + + if (ferror(fp)) { + FILE_OP_ERROR("file stream", "fread"); + g_byte_array_free(array, TRUE); + return NULL; + } + + buf[0] = '\0'; + g_byte_array_append(array, buf, 1); + str = (gchar *)array->data; + g_byte_array_free(array, FALSE); + + return str; +} + +#if defined(G_OS_WIN32) && !GLIB_CHECK_VERSION(2, 8, 2) +static gchar **argv_utf8_to_locale(gchar **argv) +{ + gint argc = 0, i; + gchar **cp_argv; + + while (argv[argc] != NULL) + argc++; + + cp_argv = g_new(gchar *, argc + 1); + + for (i = 0; i < argc; i++) { + cp_argv[i] = g_locale_from_utf8(argv[i], -1, NULL, NULL, NULL); + if (cp_argv[i] == NULL) { + g_warning("Failed to convert from UTF-8 to locale encoding: %s\n", argv[i]); + g_strfreev(cp_argv); + return NULL; + } + } + cp_argv[i] = NULL; + + return cp_argv; +} +#endif + +gint execute_async(gchar *const argv[]) +{ +#if defined(G_OS_WIN32) && !GLIB_CHECK_VERSION(2, 8, 2) + gchar **cp_argv; + + g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1); + + cp_argv = argv_utf8_to_locale((gchar **)argv); + if (!cp_argv) + return -1; + if (g_spawn_async(NULL, cp_argv, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, NULL) == FALSE) { + g_warning("Can't execute command: %s\n", argv[0]); + g_strfreev(cp_argv); + return -1; + } + g_strfreev(cp_argv); +#else + g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1); + + if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, NULL) == FALSE) { + g_warning("Can't execute command: %s\n", argv[0]); + return -1; + } +#endif + + return 0; +} + +gint execute_sync(gchar *const argv[]) +{ + gint status; +#if defined(G_OS_WIN32) && !GLIB_CHECK_VERSION(2, 8, 2) + gchar **cp_argv; +#endif + + g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1); + +#ifdef G_OS_WIN32 +#if !GLIB_CHECK_VERSION(2, 8, 2) + cp_argv = argv_utf8_to_locale((gchar **)argv); + if (!cp_argv) + return -1; + if (g_spawn_sync(NULL, cp_argv, NULL, + G_SPAWN_SEARCH_PATH | G_SPAWN_CHILD_INHERITS_STDIN | + G_SPAWN_LEAVE_DESCRIPTORS_OPEN, + NULL, NULL, NULL, NULL, &status, NULL) == FALSE) { + g_warning("Can't execute command: %s\n", argv[0]); + g_strfreev(cp_argv); + return -1; + } + g_strfreev(cp_argv); +#else /* !GLIB_CHECK_VERSION */ + if (g_spawn_sync(NULL, (gchar **)argv, NULL, + G_SPAWN_SEARCH_PATH | G_SPAWN_CHILD_INHERITS_STDIN | + G_SPAWN_LEAVE_DESCRIPTORS_OPEN, + NULL, NULL, NULL, NULL, &status, NULL) == FALSE) { + g_warning("Can't execute command: %s\n", argv[0]); + return -1; + } +#endif /* !GLIB_CHECK_VERSION */ + + return status; +#else /* G_OS_WIN32 */ + if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, NULL, &status, NULL) == FALSE) { + g_warning("Can't execute command: %s\n", argv[0]); + return -1; + } + + if (WIFEXITED(status)) + return WEXITSTATUS(status); + else + return -1; +#endif /* G_OS_WIN32 */ +} + +gint execute_command_line(const gchar *cmdline, gboolean async) +{ + gchar **argv; + gint ret; + + if (debug_mode) { + gchar *utf8_cmdline; + + utf8_cmdline = g_filename_to_utf8 + (cmdline, -1, NULL, NULL, NULL); + debug_print("execute_command_line(): executing: %s\n", + utf8_cmdline ? utf8_cmdline : cmdline); + g_free(utf8_cmdline); + } + + argv = strsplit_with_quote(cmdline, " ", 0); + + if (async) + ret = execute_async(argv); + else + ret = execute_sync(argv); + + g_strfreev(argv); + + return ret; +} + +#if USE_THREADS +typedef struct _CmdData +{ + const gchar *cmdline; + volatile gint flag; + gint status; +} CmdData; + +static gpointer execute_command_line_async_func(gpointer data) +{ + CmdData *cmd_data = (CmdData *)data; + gchar **argv; + + argv = strsplit_with_quote(cmd_data->cmdline, " ", 0); + cmd_data->status = execute_sync(argv); + g_strfreev(argv); + + debug_print("execute_command_line_async_func: exec done: %s\n", + cmd_data->cmdline); + g_atomic_int_set(&cmd_data->flag, 1); + g_main_context_wakeup(NULL); + + return GINT_TO_POINTER(0); +} + +gint execute_command_line_async_wait(const gchar *cmdline) +{ + CmdData data = {NULL, 0, 0}; + GThread *thread; + + if (debug_mode) { + gchar *utf8_cmdline; + + utf8_cmdline = g_filename_to_utf8 + (cmdline, -1, NULL, NULL, NULL); + debug_print("execute_command_line(): executing: %s\n", + utf8_cmdline ? utf8_cmdline : cmdline); + g_free(utf8_cmdline); + } + + data.cmdline = cmdline; + thread = g_thread_create(execute_command_line_async_func, &data, TRUE, + NULL); + if (!thread) + return -1; + + debug_print("execute_command_line_async_wait: waiting thread\n"); + while (g_atomic_int_get(&data.flag) == 0) + event_loop_iterate(); + + g_thread_join(thread); + debug_print("execute_command_line_async_wait: thread exited\n"); + + return data.status; +} +#else /* USE_THREADS */ +gint execute_command_line_async_wait(const gchar *cmdline) +{ + return execute_command_line(cmdline, FALSE); +} +#endif /* USE_THREADS */ + +gint execute_open_file(const gchar *file, const gchar *content_type) +{ +#ifdef G_OS_WIN32 + g_return_val_if_fail(file != NULL, -1); + + log_print("opening %s - %s\n", file, content_type ? content_type : ""); + + if (G_WIN32_HAVE_WIDECHAR_API()) { + wchar_t *wpath; + + wpath = g_utf8_to_utf16(file, -1, NULL, NULL, NULL); + if (wpath == NULL) + return -1; + + ShellExecuteW(NULL, L"open", wpath, NULL, NULL, SW_SHOWNORMAL); + + g_free(wpath); + + return 0; + } else { + gchar *cp_path; + + cp_path = g_locale_from_utf8(file, -1, NULL, NULL, NULL); + if (cp_path == NULL) + return -1; + + ShellExecuteA(NULL, "open", cp_path, NULL, NULL, SW_SHOWNORMAL); + + g_free(cp_path); + + return 0; + } +#elif defined(__APPLE__) + const gchar *argv[3] = {"open", NULL, NULL}; + + g_return_val_if_fail(file != NULL, -1); + + log_print("opening %s - %s\n", file, content_type ? content_type : ""); + + argv[1] = file; + execute_async(argv); +#else + const gchar *argv[3] = {"xdg-open", NULL, NULL}; + + g_return_val_if_fail(file != NULL, -1); + + log_print("opening %s - %s\n", file, content_type ? content_type : ""); + + argv[1] = file; + execute_async(argv); +#endif + + return 0; +} + +gint execute_print_file(const gchar *file) +{ + g_return_val_if_fail(file != NULL, -1); + +#ifdef G_OS_WIN32 + log_print("printing %s\n", file); + + if (G_WIN32_HAVE_WIDECHAR_API()) { + wchar_t *wpath; + + wpath = g_utf8_to_utf16(file, -1, NULL, NULL, NULL); + if (wpath == NULL) + return -1; + + ShellExecuteW(NULL, L"print", wpath, NULL, NULL, SW_SHOWNORMAL); + + g_free(wpath); + + return 0; + } else { + gchar *cp_path; + + cp_path = g_locale_from_utf8(file, -1, NULL, NULL, NULL); + if (cp_path == NULL) + return -1; + + ShellExecuteA(NULL, "print", cp_path, NULL, NULL, + SW_SHOWNORMAL); + + g_free(cp_path); + + return 0; + } +#endif + return 0; +} + +gchar *get_command_output(const gchar *cmdline) +{ + gchar *child_stdout; + gint status; + + g_return_val_if_fail(cmdline != NULL, NULL); + + debug_print("get_command_output(): executing: %s\n", cmdline); + + if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status, + NULL) == FALSE) { + g_warning("Can't execute command: %s\n", cmdline); + return NULL; + } + + return child_stdout; +} + +gint open_uri(const gchar *uri, const gchar *cmdline) +{ + gchar buf[BUFFSIZE]; + + g_return_val_if_fail(uri != NULL, -1); + +#if defined(G_OS_WIN32) || defined(__APPLE__) + if (!cmdline || cmdline[0] == '\0') + return execute_open_file(uri, NULL); +#endif + + if (cmdline && str_find_format_times(cmdline, 's') == 1) + g_snprintf(buf, sizeof(buf), cmdline, uri); + else { + if (cmdline) + g_warning("Open URI command line is invalid " + "(there must be only one '%%s'): %s", + cmdline); + g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, uri); + } + + execute_command_line(buf, TRUE); + + return 0; +} + +gint play_sound(const gchar *file, gboolean async) +{ +#ifdef G_OS_WIN32 + wchar_t *wfile; + DWORD flag = SND_FILENAME; + + wfile = g_utf8_to_utf16(file, -1, NULL, NULL, NULL); + if (wfile == NULL) + return -1; + if (async) + flag |= SND_ASYNC; + else + flag |= SND_SYNC; + PlaySoundW(wfile, NULL, flag); + g_free(wfile); +#endif + return 0; +} + +stime_t remote_tzoffset_sec(const gchar *zone) +{ + static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT"; + gchar zone3[4]; + gchar *p; + gchar c; + gint iustz; + gint offset; + time_t remoteoffset; + + strncpy(zone3, zone, 3); + zone3[3] = '\0'; + remoteoffset = 0; + + if (sscanf(zone, "%c%d", &c, &offset) == 2 && + (c == '+' || c == '-')) { + remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60; + if (c == '-') + remoteoffset = -remoteoffset; + } else if (!strncmp(zone, "UT" , 2) || + !strncmp(zone, "GMT", 2)) { + remoteoffset = 0; + } else if (strlen(zone3) == 3) { + for (p = ustzstr; *p != '\0'; p += 3) { + if (!g_ascii_strncasecmp(p, zone3, 3)) { + iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8; + remoteoffset = iustz * 3600; + break; + } + } + if (*p == '\0') + return -1; + } else if (strlen(zone3) == 1) { + switch (zone[0]) { + case 'Z': remoteoffset = 0; break; + case 'A': remoteoffset = -1; break; + case 'B': remoteoffset = -2; break; + case 'C': remoteoffset = -3; break; + case 'D': remoteoffset = -4; break; + case 'E': remoteoffset = -5; break; + case 'F': remoteoffset = -6; break; + case 'G': remoteoffset = -7; break; + case 'H': remoteoffset = -8; break; + case 'I': remoteoffset = -9; break; + case 'K': remoteoffset = -10; break; /* J is not used */ + case 'L': remoteoffset = -11; break; + case 'M': remoteoffset = -12; break; + case 'N': remoteoffset = 1; break; + case 'O': remoteoffset = 2; break; + case 'P': remoteoffset = 3; break; + case 'Q': remoteoffset = 4; break; + case 'R': remoteoffset = 5; break; + case 'S': remoteoffset = 6; break; + case 'T': remoteoffset = 7; break; + case 'U': remoteoffset = 8; break; + case 'V': remoteoffset = 9; break; + case 'W': remoteoffset = 10; break; + case 'X': remoteoffset = 11; break; + case 'Y': remoteoffset = 12; break; + default: remoteoffset = 0; break; + } + remoteoffset = remoteoffset * 3600; + } else + return -1; + + return remoteoffset; +} + +stime_t tzoffset_sec(stime_t *now) +{ + time_t now_ = *now; + struct tm gmt, *tmp, *lt; + gint off; + + tmp = gmtime(&now_); + g_return_val_if_fail(tmp != NULL, -1); + gmt = *tmp; + lt = localtime(&now_); + g_return_val_if_fail(lt != NULL, -1); + + off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min; + + if (lt->tm_year < gmt.tm_year) + off -= 24 * 60; + else if (lt->tm_year > gmt.tm_year) + off += 24 * 60; + else if (lt->tm_yday < gmt.tm_yday) + off -= 24 * 60; + else if (lt->tm_yday > gmt.tm_yday) + off += 24 * 60; + + if (off >= 24 * 60) /* should be impossible */ + off = 23 * 60 + 59; /* if not, insert silly value */ + if (off <= -24 * 60) + off = -(23 * 60 + 59); + + return off * 60; +} + +/* calculate timezone offset (buf must not be less than 6 bytes) */ +gchar *tzoffset_buf(gchar *buf, stime_t *now) +{ + time_t now_ = *now; + struct tm gmt, *tmp, *lt; + gint off; + gchar sign = '+'; + + tmp = gmtime(&now_); + g_return_val_if_fail(tmp != NULL, NULL); + gmt = *tmp; + lt = localtime(&now_); + g_return_val_if_fail(lt != NULL, NULL); + + off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min; + + if (lt->tm_year < gmt.tm_year) + off -= 24 * 60; + else if (lt->tm_year > gmt.tm_year) + off += 24 * 60; + else if (lt->tm_yday < gmt.tm_yday) + off -= 24 * 60; + else if (lt->tm_yday > gmt.tm_yday) + off += 24 * 60; + + if (off < 0) { + sign = '-'; + off = -off; + } + + if (off >= 24 * 60) /* should be impossible */ + off = 23 * 60 + 59; /* if not, insert silly value */ + + g_snprintf(buf, 6, "%c%02d%02d", sign, off / 60, off % 60); + + return buf; +} + +gchar *tzoffset(stime_t *now) +{ + static gchar offset_string[6]; + + return tzoffset_buf(offset_string, now); +} + +void get_rfc822_date(gchar *buf, gint len) +{ + struct tm *lt; + time_t t; + stime_t t_; + gchar day[4], mon[4]; + gint dd, hh, mm, ss, yyyy; + gchar off[6]; + + t_ = t = time(NULL); + lt = localtime(&t); + + sscanf(asctime(lt), "%3s %3s %d %d:%d:%d %d\n", + day, mon, &dd, &hh, &mm, &ss, &yyyy); + g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s", + day, dd, mon, yyyy, hh, mm, ss, tzoffset_buf(off, &t_)); +} + +/* just a wrapper to suppress the warning of gcc about %c */ +size_t my_strftime(gchar *s, size_t max, const gchar *format, + const struct tm *tm) +{ + return strftime(s, max, format, tm); +} + +/* UI hints */ + +static UIUpdateFunc ui_update_func = NULL; + +void set_ui_update_func(UIUpdateFunc func) +{ + ui_update_func = func; +} + +void ui_update(void) +{ + if (ui_update_func) + ui_update_func(); +} + +static EventLoopFunc event_loop_func = NULL; + +void set_event_loop_func(EventLoopFunc func) +{ + event_loop_func = func; +} + +void event_loop_iterate(void) +{ + if (event_loop_func) + event_loop_func(); + else + g_main_context_iteration(NULL, TRUE); +} + +static ProgressFunc progress_func = NULL; + +void set_progress_func(ProgressFunc func) +{ + progress_func = func; +} + +void progress_show(gint cur, gint total) +{ + if (progress_func) + progress_func(cur, total); +} + +/* user input */ + +static QueryPasswordFunc query_password_func = NULL; + +void set_input_query_password_func(QueryPasswordFunc func) +{ + query_password_func = func; +} + +gchar *input_query_password(const gchar *server, const gchar *user) +{ + if (query_password_func) + return query_password_func(server, user); + else + return NULL; +} + +/* logging */ + +static FILE *log_fp = NULL; +#if USE_THREADS +G_LOCK_DEFINE_STATIC(log_fp); +#define S_LOCK(name) G_LOCK(name) +#define S_UNLOCK(name) G_UNLOCK(name) +#else +#define S_LOCK(name) +#define S_UNLOCK(name) +#endif + +void set_log_file(const gchar *filename) +{ + S_LOCK(log_fp); + if (!log_fp) { + log_fp = g_fopen(filename, "w"); + if (!log_fp) + FILE_OP_ERROR(filename, "fopen"); + } + S_UNLOCK(log_fp); +} + +void close_log_file(void) +{ + S_LOCK(log_fp); + if (log_fp) { + fclose(log_fp); + log_fp = NULL; + } + S_UNLOCK(log_fp); +} + +static guint log_verbosity_count = 0; + +void set_log_verbosity(gboolean verbose) +{ + if (verbose) + log_verbosity_count++; + else if (log_verbosity_count > 0) + log_verbosity_count--; +} + +gboolean get_debug_mode(void) +{ + return debug_mode; +} + +void set_debug_mode(gboolean enable) +{ + debug_mode = enable; +} + +static void log_dummy_func(const gchar *str) +{ +} + +static void log_dummy_flush_func(void) +{ +} + +static LogFunc log_print_ui_func = log_dummy_func; +static LogFunc log_message_ui_func = log_dummy_func; +static LogFunc log_warning_ui_func = log_dummy_func; +static LogFunc log_error_ui_func = log_dummy_func; +static LogFlushFunc log_flush_ui_func = log_dummy_flush_func; + +static LogFunc log_show_status_func = log_dummy_func; + +void set_log_ui_func(LogFunc print_func, LogFunc message_func, + LogFunc warning_func, LogFunc error_func) +{ + log_print_ui_func = print_func; + log_message_ui_func = message_func; + log_warning_ui_func = warning_func; + log_error_ui_func = error_func; +} + +void set_log_ui_func_full(LogFunc print_func, LogFunc message_func, + LogFunc warning_func, LogFunc error_func, + LogFlushFunc flush_func) +{ + set_log_ui_func(print_func, message_func, warning_func, error_func); + log_flush_ui_func = flush_func; +} + +void set_log_show_status_func(LogFunc status_func) +{ + log_show_status_func = status_func; +} + +void debug_print(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE]; + + if (!debug_mode) return; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + g_print("%s", buf); +} + +void status_print(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE]; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + log_show_status_func(buf); +} + +#define TIME_LEN 11 + +void log_write(const gchar *str, const gchar *prefix) +{ + S_LOCK(log_fp); + + if (log_fp) { + gchar buf[TIME_LEN + 1]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + fputs(buf, log_fp); + if (prefix) + fputs(prefix, log_fp); + fputs(str, log_fp); + fflush(log_fp); + } + + S_UNLOCK(log_fp); +} + +void log_print(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE + TIME_LEN]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + va_start(args, format); + g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args); + va_end(args); + + if (debug_mode) g_print("%s", buf); + log_print_ui_func(buf); + S_LOCK(log_fp); + if (log_fp) { + fputs(buf, log_fp); + fflush(log_fp); + } + S_UNLOCK(log_fp); + if (log_verbosity_count) + log_show_status_func(buf + TIME_LEN); +} + +void log_message(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE + TIME_LEN]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + va_start(args, format); + g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args); + va_end(args); + + if (debug_mode) g_message("%s", buf + TIME_LEN); + log_message_ui_func(buf + TIME_LEN); + S_LOCK(log_fp); + if (log_fp) { + fwrite(buf, TIME_LEN, 1, log_fp); + fputs("* message: ", log_fp); + fputs(buf + TIME_LEN, log_fp); + fflush(log_fp); + } + S_UNLOCK(log_fp); + log_show_status_func(buf + TIME_LEN); +} + +void log_warning(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE + TIME_LEN]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + va_start(args, format); + g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args); + va_end(args); + + g_warning("%s", buf); + log_warning_ui_func(buf + TIME_LEN); + S_LOCK(log_fp); + if (log_fp) { + fwrite(buf, TIME_LEN, 1, log_fp); + fputs("** warning: ", log_fp); + fputs(buf + TIME_LEN, log_fp); + fflush(log_fp); + } + S_UNLOCK(log_fp); +} + +void log_error(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE + TIME_LEN]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + va_start(args, format); + g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args); + va_end(args); + + g_warning("%s", buf); + log_error_ui_func(buf + TIME_LEN); + S_LOCK(log_fp); + if (log_fp) { + fwrite(buf, TIME_LEN, 1, log_fp); + fputs("*** error: ", log_fp); + fputs(buf + TIME_LEN, log_fp); + fflush(log_fp); + } + S_UNLOCK(log_fp); +} + +void log_flush(void) +{ + S_LOCK(log_fp); + if (log_fp) + fflush(log_fp); + S_UNLOCK(log_fp); + log_flush_ui_func(); +} diff --git a/libsylph/utils.h b/libsylph/utils.h new file mode 100644 index 0000000..9ac65cf --- /dev/null +++ b/libsylph/utils.h @@ -0,0 +1,599 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2017 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_ALLOCA_H +# include +#endif + +/* Wrappers for C library function that take pathname arguments. */ +#if GLIB_CHECK_VERSION(2, 6, 0) +# include +#else + +#define g_open open +#define g_rename rename +#define g_mkdir mkdir +#define g_stat stat +#define g_lstat lstat +#define g_unlink unlink +#define g_remove remove +#define g_rmdir rmdir +#define g_fopen fopen +#define g_freopen freopen + +#endif /* GLIB_CHECK_VERSION */ + +#if !GLIB_CHECK_VERSION(2, 7, 0) + +#ifdef G_OS_UNIX +#define g_chdir chdir +#define g_chmod chmod +#else +gint g_chdir (const gchar *path); +gint g_chmod (const gchar *path, + gint mode); +#endif /* G_OS_UNIX */ + +#endif /* !GLIB_CHECK_VERSION */ + +#ifdef G_OS_UNIX +#define syl_link link +#else +gint syl_link (const gchar *src, + const gchar *dest); +#endif + +/* The AC_CHECK_SIZEOF() in configure fails for some machines. + * we provide some fallback values here */ +#if !SIZEOF_UNSIGNED_SHORT + #undef SIZEOF_UNSIGNED_SHORT + #define SIZEOF_UNSIGNED_SHORT 2 +#endif +#if !SIZEOF_UNSIGNED_INT + #undef SIZEOF_UNSIGNED_INT + #define SIZEOF_UNSIGNED_INT 4 +#endif +#if !SIZEOF_UNSIGNED_LONG + #undef SIZEOF_UNSIGNED_LONG + #define SIZEOF_UNSIGNED_LONG 4 +#endif + +#ifndef HAVE_U32_TYPEDEF + #undef u32 /* maybe there is a macro with this name */ + typedef guint32 u32; + #define HAVE_U32_TYPEDEF +#endif + +#if defined(G_OS_WIN32) && !defined(_WIN64) && defined(HAVE_64BIT_TIME_T) + /* for backward binary compatibility. Use only in struct definition and + pointer arguments. */ + typedef gint32 stime_t; +#else + typedef time_t stime_t; +#endif + +#if !GLIB_CHECK_VERSION(2, 26, 0) +#if (defined (_MSC_VER) || defined (__MINGW32__)) && !defined(_WIN64) + typedef struct _stat32 GStatBuf; +#else + typedef struct stat GStatBuf; +#endif +#endif + +#ifndef BIG_ENDIAN_HOST + #if (G_BYTE_ORDER == G_BIG_ENDIAN) + #define BIG_ENDIAN_HOST 1 + #endif +#endif + +#define CHDIR_RETURN_IF_FAIL(dir) \ +{ \ + if (change_dir(dir) < 0) return; \ +} + +#define CHDIR_RETURN_VAL_IF_FAIL(dir, val) \ +{ \ + if (change_dir(dir) < 0) return val; \ +} + +#define Xalloca(ptr, size, iffail) \ +{ \ + if ((ptr = alloca(size)) == NULL) { \ + g_warning("can't allocate memory\n"); \ + iffail; \ + } \ +} + +#define Xstrdup_a(ptr, str, iffail) \ +{ \ + gchar *__tmp; \ + \ + if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \ + g_warning("can't allocate memory\n"); \ + iffail; \ + } else \ + strcpy(__tmp, str); \ + \ + ptr = __tmp; \ +} + +#define Xstrndup_a(ptr, str, len, iffail) \ +{ \ + gchar *__tmp; \ + \ + if ((__tmp = alloca(len + 1)) == NULL) { \ + g_warning("can't allocate memory\n"); \ + iffail; \ + } else { \ + strncpy(__tmp, str, len); \ + __tmp[len] = '\0'; \ + } \ + \ + ptr = __tmp; \ +} + +#define Xstrcat_a(ptr, str1, str2, iffail) \ +{ \ + gchar *__tmp; \ + gint len1, len2; \ + \ + len1 = strlen(str1); \ + len2 = strlen(str2); \ + if ((__tmp = alloca(len1 + len2 + 1)) == NULL) { \ + g_warning("can't allocate memory\n"); \ + iffail; \ + } else { \ + memcpy(__tmp, str1, len1); \ + memcpy(__tmp + len1, str2, len2 + 1); \ + } \ + \ + ptr = __tmp; \ +} + +#define AUTORELEASE_STR(str, iffail) \ +{ \ + gchar *__str; \ + Xstrdup_a(__str, str, iffail); \ + g_free(str); \ + str = __str; \ +} + +#define FILE_OP_ERROR(file, func) \ +{ \ + fprintf(stderr, "%s: ", file); \ + fflush(stderr); \ + perror(func); \ +} + +typedef void (*UIUpdateFunc) (void); +typedef void (*EventLoopFunc) (void); +typedef void (*ProgressFunc) (gint cur, + gint total); +typedef gchar * (*QueryPasswordFunc) (const gchar *server, + const gchar *user); +typedef void (*LogFunc) (const gchar *str); +typedef void (*LogFlushFunc) (void); + +/* for macro expansion */ +#define Str(x) #x +#define Xstr(x) Str(x) + +void list_free_strings (GList *list); +void slist_free_strings (GSList *list); + +void hash_free_strings (GHashTable *table); +void hash_free_value_mem (GHashTable *table); + +gint str_case_equal (gconstpointer v, + gconstpointer v2); +guint str_case_hash (gconstpointer key); + +void ptr_array_free_strings (GPtrArray *array); + +typedef gboolean (*StrFindFunc) (const gchar *haystack, + const gchar *needle); + +gboolean str_find (const gchar *haystack, + const gchar *needle); +gboolean str_case_find (const gchar *haystack, + const gchar *needle); +gboolean str_find_equal (const gchar *haystack, + const gchar *needle); +gboolean str_case_find_equal (const gchar *haystack, + const gchar *needle); + +/* number-string conversion */ +gint to_number (const gchar *nstr); +guint to_unumber (const gchar *nstr); +gchar *itos_buf (gchar *nstr, + gint n); +gchar *itos (gint n); +gchar *utos_buf (gchar *nstr, + guint n); +gchar *to_human_readable_buf (gchar *buf, + size_t bufsize, + gint64 size); +gchar *to_human_readable (gint64 size); + +/* alternative string functions */ +gint strcmp2 (const gchar *s1, + const gchar *s2); +gint path_cmp (const gchar *s1, + const gchar *s2); +gboolean is_path_parent (const gchar *parent, + const gchar *child); + +gchar *strretchomp (gchar *str); +gchar *strtailchomp (gchar *str, + gchar tail_char); +gchar *strcrchomp (gchar *str); + +gchar *strcasestr (const gchar *haystack, + const gchar *needle); +gpointer my_memmem (gconstpointer haystack, + size_t haystacklen, + gconstpointer needle, + size_t needlelen); + +gchar *strncpy2 (gchar *dest, + const gchar *src, + size_t n); + +gboolean str_has_suffix_case (const gchar *str, + const gchar *suffix); + +gint str_find_format_times (const gchar *haystack, + gchar ch); + +gboolean is_next_nonascii (const gchar *s); +gint get_next_word_len (const gchar *s); + +/* functions for string parsing */ +gint subject_compare (const gchar *s1, + const gchar *s2); +gint subject_compare_for_sort (const gchar *s1, + const gchar *s2); +void trim_subject_for_compare (gchar *str); +void trim_subject_for_sort (gchar *str); +void trim_subject (gchar *str); +void eliminate_parenthesis (gchar *str, + gchar op, + gchar cl); +void extract_parenthesis (gchar *str, + gchar op, + gchar cl); +void extract_parenthesis_with_escape (gchar *str, + gchar op, + gchar cl); + +void extract_parenthesis_with_skip_quote (gchar *str, + gchar quote_chr, + gchar op, + gchar cl); + +void eliminate_quote (gchar *str, + gchar quote_chr); +void extract_quote (gchar *str, + gchar quote_chr); +void extract_quote_with_escape (gchar *str, + gchar quote_chr); +void eliminate_address_comment (gchar *str); +gchar *strchr_with_skip_quote (const gchar *str, + gint quote_chr, + gint c); +gchar *strrchr_with_skip_quote (const gchar *str, + gint quote_chr, + gint c); +void extract_address (gchar *str); +void extract_list_id_str (gchar *str); + +gchar *extract_addresses (const gchar *str); + +gchar *normalize_address_field (const gchar *str); + +gboolean address_equal (const gchar *addr1, + const gchar *addr2); + +GSList *address_list_append_orig (GSList *addr_list, + const gchar *str); +GSList *address_list_append (GSList *addr_list, + const gchar *str); +GSList *references_list_prepend (GSList *msgid_list, + const gchar *str); +GSList *references_list_append (GSList *msgid_list, + const gchar *str); +GSList *newsgroup_list_append (GSList *group_list, + const gchar *str); + +GList *add_history (GList *list, + const gchar *str); + +/* modify string */ +void remove_return (gchar *str); +void remove_space (gchar *str); +void unfold_line (gchar *str); +void subst_char (gchar *str, + gchar orig, + gchar subst); +void subst_chars (gchar *str, + gchar *orig, + gchar subst); +void subst_null (gchar *str, + gint len, + gchar subst); +void subst_control (gchar *str, + gchar subst); +void subst_for_filename (gchar *str); + +gchar *get_alt_filename (const gchar *filename, + gint count); + +gboolean is_header_line (const gchar *str); +gboolean is_ascii_str (const gchar *str); + +gint get_quote_level (const gchar *str); +gint check_line_length (const gchar *str, + gint max_chars, + gint *line); + +gchar *strstr_with_skip_quote (const gchar *haystack, + const gchar *needle); +gchar *strcasestr_with_skip_quote (const gchar *haystack, + const gchar *needle); +gchar *strchr_parenthesis_close (const gchar *str, + gchar op, + gchar cl); + +gchar **strsplit_parenthesis (const gchar *str, + gchar op, + gchar cl, + gint max_tokens); +gchar **strsplit_with_quote (const gchar *str, + const gchar *delim, + gint max_tokens); +gchar **strsplit_csv (const gchar *str, + gchar delim, + gint max_tokens); + +gchar *strconcat_csv (gchar delim, + const gchar *field1, + ...) G_GNUC_MALLOC G_GNUC_NULL_TERMINATED; + +gchar *get_abbrev_newsgroup_name (const gchar *group, + gint len); +gchar *trim_string (const gchar *str, + gint len); +gchar *trim_string_before (const gchar *str, + gint len); + +GList *uri_list_extract_filenames (const gchar *uri_list); +gboolean is_uri_string (const gchar *str); +gchar *get_uri_path (const gchar *uri); +gint get_uri_len (const gchar *str); +void decode_uri (gchar *decoded_uri, + const gchar *encoded_uri); +void decode_xdigit_encoded_str (gchar *decoded, + const gchar *encoded); +gchar *encode_uri (const gchar *filename); +gchar *uriencode_for_filename (const gchar *filename); +gchar *uriencode_for_mailto (const gchar *mailto); +gint scan_mailto_url (const gchar *mailto, + gchar **to, + gchar **cc, + gchar **bcc, + gchar **subject, + gchar **inreplyto, + gchar **body); + +void set_startup_dir (void); +void set_rc_dir (const gchar *dir); + +/* return static strings */ +const gchar *get_startup_dir (void); +const gchar *get_home_dir (void); +const gchar *get_document_dir (void); +const gchar *get_rc_dir (void); +const gchar *get_old_rc_dir (void); +const gchar *get_mail_base_dir (void); +const gchar *get_news_cache_dir (void); +const gchar *get_imap_cache_dir (void); +const gchar *get_mime_tmp_dir (void); +const gchar *get_template_dir (void); +const gchar *get_tmp_dir (void); +gchar *get_tmp_file (void); +const gchar *get_domain_name (void); + +/* file / directory handling */ +off_t get_file_size (const gchar *file); +off_t get_file_size_as_crlf (const gchar *file); +off_t get_left_file_size (FILE *fp); + +gint get_last_empty_line_size (FILE *fp, + off_t size); + +gboolean file_exist (const gchar *file, + gboolean allow_fifo); +gboolean is_dir_exist (const gchar *dir); +gboolean is_file_entry_exist (const gchar *file); +gboolean dirent_is_regular_file (struct dirent *d); +gboolean dirent_is_directory (struct dirent *d); + +#define is_file_exist(file) file_exist(file, FALSE) +#define is_file_or_fifo_exist(file) file_exist(file, TRUE) + +gint change_dir (const gchar *dir); +gint make_dir (const gchar *dir); +gint make_dir_hier (const gchar *dir); +gint remove_all_files (const gchar *dir); +gint remove_numbered_files (const gchar *dir, + guint first, + guint last); +gint remove_all_numbered_files (const gchar *dir); +gint remove_expired_files (const gchar *dir, + guint hours); +gint remove_dir_recursive (const gchar *dir); +gint rename_force (const gchar *oldpath, + const gchar *newpath); +gint copy_file (const gchar *src, + const gchar *dest, + gboolean keep_backup); +gint copy_dir (const gchar *src, + const gchar *dest); +gint move_file (const gchar *src, + const gchar *dest, + gboolean overwrite); + +gint append_file_part (FILE *fp, + off_t offset, + size_t length, + FILE *dest_fp); +gint copy_file_part (FILE *fp, + off_t offset, + size_t length, + const gchar *dest); + +gint copy_file_stream (FILE *fp, + FILE *dest_fp); + +gchar *canonicalize_str (const gchar *str); +gint canonicalize_file (const gchar *src, + const gchar *dest); +gint canonicalize_file_replace (const gchar *file); +FILE *canonicalize_file_stream (FILE *fp, + gint *length); +gint uncanonicalize_file (const gchar *src, + const gchar *dest); +gint uncanonicalize_file_replace(const gchar *file); + +gchar *normalize_newlines (const gchar *str); +gchar *strchomp_all (const gchar *str); + +FILE *get_outgoing_rfc2822_file (FILE *fp); +gchar *get_outgoing_rfc2822_str (FILE *fp); +gchar *generate_mime_boundary (const gchar *prefix); + +gint change_file_mode_rw (FILE *fp, + const gchar *file); +FILE *my_tmpfile (void); +FILE *str_open_as_stream (const gchar *str); +gint str_write_to_file (const gchar *str, + const gchar *file); +gchar *file_read_to_str (const gchar *file); +gchar *file_read_stream_to_str (FILE *fp); + +/* process execution */ +gint execute_async (gchar *const argv[]); +gint execute_sync (gchar *const argv[]); +gint execute_command_line (const gchar *cmdline, + gboolean async); +gint execute_command_line_async_wait + (const gchar *cmdline); + +gint execute_open_file (const gchar *file, + const gchar *content_type); +gint execute_print_file (const gchar *file); +gchar *get_command_output (const gchar *cmdline); + +/* open URI with external browser */ +gint open_uri (const gchar *uri, + const gchar *cmdline); + +/* play sound */ +gint play_sound (const gchar *file, + gboolean async); + +/* time functions */ +stime_t remote_tzoffset_sec (const gchar *zone); +stime_t tzoffset_sec (stime_t *now); +gchar *tzoffset_buf (gchar *buf, + stime_t *now); +gchar *tzoffset (stime_t *now); +void get_rfc822_date (gchar *buf, + gint len); + +size_t my_strftime (gchar *s, + size_t max, + const gchar *format, + const struct tm *tm); + +/* UI hints */ +void set_ui_update_func (UIUpdateFunc func); +void ui_update (void); + +void set_event_loop_func (EventLoopFunc func); +void event_loop_iterate (void); + +void set_progress_func (ProgressFunc func); +void progress_show (gint cur, + gint total); + +/* user input */ +void set_input_query_password_func (QueryPasswordFunc func); + +gchar *input_query_password (const gchar *server, + const gchar *user); + +/* logging */ +void set_log_file (const gchar *filename); +void close_log_file (void); +void set_log_verbosity (gboolean verbose); +gboolean get_debug_mode (void); +void set_debug_mode (gboolean enable); + +void set_log_ui_func (LogFunc print_func, + LogFunc message_func, + LogFunc warning_func, + LogFunc error_func); +void set_log_ui_func_full (LogFunc print_func, + LogFunc message_func, + LogFunc warning_func, + LogFunc error_func, + LogFlushFunc flush_func); + +void set_log_show_status_func (LogFunc status_func); + +void debug_print (const gchar *format, ...) G_GNUC_PRINTF(1, 2); +void status_print (const gchar *format, ...) G_GNUC_PRINTF(1, 2); + +void log_write (const gchar *str, + const gchar *prefix); + +void log_print (const gchar *format, ...) G_GNUC_PRINTF(1, 2); +void log_message (const gchar *format, ...) G_GNUC_PRINTF(1, 2); +void log_warning (const gchar *format, ...) G_GNUC_PRINTF(1, 2); +void log_error (const gchar *format, ...) G_GNUC_PRINTF(1, 2); + +void log_flush (void); + +#endif /* __UTILS_H__ */ diff --git a/libsylph/uuencode.c b/libsylph/uuencode.c new file mode 100644 index 0000000..090e3d3 --- /dev/null +++ b/libsylph/uuencode.c @@ -0,0 +1,101 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#define UUDECODE(c) (c=='`' ? 0 : c - ' ') +#define N64(i) (i & ~63) + +const char uudigit[64] = +{ + '`', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_' +}; + +int touufrombits(unsigned char *out, const unsigned char *in, int inlen) +{ + int len; + + if (inlen > 45) return -1; + len = (inlen * 4 + 2) / 3 + 1; + *out++ = uudigit[inlen]; + + for (; inlen >= 3; inlen -= 3) { + *out++ = uudigit[in[0] >> 2]; + *out++ = uudigit[((in[0] << 4) & 0x30) | (in[1] >> 4)]; + *out++ = uudigit[((in[1] << 2) & 0x3c) | (in[2] >> 6)]; + *out++ = uudigit[in[2] & 0x3f]; + in += 3; + } + + if (inlen > 0) { + *out++ = uudigit[(in[0] >> 2)]; + if (inlen == 1) { + *out++ = uudigit[((in[0] << 4) & 0x30)]; + } else { + *out++ = uudigit[(((in[0] << 4) & 0x30) | (in[1] >> 4))] ; + *out++ = uudigit[((in[1] << 2) & 0x3c)]; + } + } + *out = '\0'; + + return len; +} + +int fromuutobits(char *out, const char *in) +{ + int len, outlen, inlen; + register unsigned char digit1, digit2; + + outlen = UUDECODE(in[0]); + in += 1; + if(outlen < 0 || outlen > 45) + return -2; + if(outlen == 0) + return 0; + inlen = (outlen * 4 + 2) / 3; + len = 0; + + for( ; inlen>0; inlen-=4) { + digit1 = UUDECODE(in[0]); + if (N64(digit1)) return -1; + digit2 = UUDECODE(in[1]); + if (N64(digit2)) return -1; + out[len++] = (digit1 << 2) | (digit2 >> 4); + if (inlen > 2) { + digit1 = UUDECODE(in[2]); + if (N64(digit1)) return -1; + out[len++] = (digit2 << 4) | (digit1 >> 2); + if (inlen > 3) { + digit2 = UUDECODE(in[3]); + if (N64(digit2)) return -1; + out[len++] = (digit1 << 6) | digit2; + } + } + in += 4; + } + + return len == outlen ? len : -3; +} diff --git a/libsylph/uuencode.h b/libsylph/uuencode.h new file mode 100644 index 0000000..15a1800 --- /dev/null +++ b/libsylph/uuencode.h @@ -0,0 +1,24 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +int touufrombits(unsigned char *, const unsigned char *, int); +int fromuutobits(char *, const char *); + +#define X_UUENCODE_END_LINE '`' +#define UUENCODE_END_LINE ' ' diff --git a/libsylph/virtual.c b/libsylph/virtual.c new file mode 100644 index 0000000..ead73ee --- /dev/null +++ b/libsylph/virtual.c @@ -0,0 +1,603 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2010 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#undef MEASURE_TIME + +#include "folder.h" +#include "virtual.h" +#include "mh.h" +#include "procmsg.h" +#include "procheader.h" +#include "filter.h" +#include "utils.h" + +typedef struct _VirtualSearchInfo VirtualSearchInfo; +typedef struct _SearchCacheInfo SearchCacheInfo; + +struct _VirtualSearchInfo { + FilterRule *rule; + GSList *mlist; + GHashTable *search_cache_table; + FILE *fp; + gboolean requires_full_headers; + gboolean exclude_trash; +}; + +struct _SearchCacheInfo { + FolderItem *folder; + guint msgnum; + off_t size; + time_t mtime; + MsgFlags flags; +}; + +enum +{ + SCACHE_NOT_EXIST = 0, + SCACHE_MATCHED = 1, + SCACHE_NOT_MATCHED = 2 +}; + +static void virtual_folder_init (Folder *folder, + const gchar *name, + const gchar *path); + +static GHashTable *virtual_read_search_cache + (FolderItem *item); +static void virtual_write_search_cache (FILE *fp, + FolderItem *item, + MsgInfo *msginfo, + gint matched); + +static GSList *virtual_search_folder (VirtualSearchInfo *info, + FolderItem *item); +static gboolean virtual_search_recursive_func + (GNode *node, + gpointer data); + +static Folder *virtual_folder_new (const gchar *name, + const gchar *path); +static void virtual_folder_destroy (Folder *folder); + +static GSList *virtual_get_msg_list (Folder *folder, + FolderItem *item, + gboolean use_cache); +static gchar *virtual_fetch_msg (Folder *folder, + FolderItem *item, + gint num); +static MsgInfo *virtual_get_msginfo (Folder *folder, + FolderItem *item, + gint num); +static gint virtual_close (Folder *folder, + FolderItem *item); + +static gint virtual_scan_folder (Folder *folder, + FolderItem *item); + +static gint virtual_rename_folder (Folder *folder, + FolderItem *item, + const gchar *name); +static gint virtual_move_folder (Folder *folder, + FolderItem *item, + FolderItem *new_parent); +static gint virtual_remove_folder (Folder *folder, + FolderItem *item); + +static FolderClass virtual_class = +{ + F_VIRTUAL, + + virtual_folder_new, + virtual_folder_destroy, + + NULL, + NULL, + + virtual_get_msg_list, + NULL, + virtual_fetch_msg, + virtual_get_msginfo, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + virtual_close, + virtual_scan_folder, + + NULL, + virtual_rename_folder, + virtual_move_folder, + virtual_remove_folder, +}; + + +FolderClass *virtual_get_class(void) +{ + return &virtual_class; +} + +static Folder *virtual_folder_new(const gchar *name, const gchar *path) +{ + Folder *folder; + + folder = (Folder *)g_new0(VirtualFolder, 1); + virtual_folder_init(folder, name, path); + + return folder; +} + +static void virtual_folder_destroy(Folder *folder) +{ + folder_local_folder_destroy(LOCAL_FOLDER(folder)); +} + +static void virtual_folder_init(Folder *folder, const gchar *name, + const gchar *path) +{ + folder->klass = virtual_get_class(); + folder_local_folder_init(folder, name, path); +} + +guint sinfo_hash(gconstpointer key) +{ + const SearchCacheInfo *sinfo = key; + guint h; + + h = (guint)sinfo->folder; + h ^= sinfo->msgnum; + h ^= (guint)sinfo->size; + h ^= (guint)sinfo->mtime; + /* h ^= (guint)sinfo->flags.tmp_flags; */ + h ^= (guint)sinfo->flags.perm_flags; + + /* g_print("path: %s, n = %u, hash = %u\n", + sinfo->folder->path, sinfo->msgnum, h); */ + + return h; +} + +gint sinfo_equal(gconstpointer v, gconstpointer v2) +{ + const SearchCacheInfo *s1 = v; + const SearchCacheInfo *s2 = v2; + + return (s1->folder == s2->folder && s1->msgnum == s2->msgnum && + s1->size == s2->size && s1->mtime == s2->mtime && + /* s1->flags.tmp_flags == s2->flags.tmp_flags && */ + s1->flags.perm_flags == s2->flags.perm_flags); +} + +#define READ_CACHE_DATA_INT(n, fp) \ +{ \ + guint32 idata; \ + \ + if (fread(&idata, sizeof(idata), 1, fp) != 1) { \ + g_warning("Cache data is corrupted\n"); \ + fclose(fp); \ + return table; \ + } else \ + n = idata; \ +} + +static GHashTable *virtual_read_search_cache(FolderItem *item) +{ + GHashTable *table; + gchar *path, *file; + FILE *fp; + gchar *id; + gint count = 0; + + g_return_val_if_fail(item != NULL, NULL); + + path = folder_item_get_path(item); + file = g_strconcat(path, G_DIR_SEPARATOR_S, SEARCH_CACHE, NULL); + debug_print("reading search cache: %s\n", file); + fp = procmsg_open_data_file(file, SEARCH_CACHE_VERSION, DATA_READ, + NULL, 0); + g_free(file); + g_free(path); + if (!fp) + return NULL; + + table = g_hash_table_new(sinfo_hash, sinfo_equal); + + while (procmsg_read_cache_data_str(fp, &id) == 0) { + FolderItem *folder; + guint32 msgnum; + off_t size; + time_t mtime; + MsgFlags flags; + gint matched; + SearchCacheInfo *sinfo; + + folder = folder_find_item_from_identifier(id); + g_free(id); + + while (fread(&msgnum, sizeof(msgnum), 1, fp) == 1) { + if (msgnum == 0) + break; + + READ_CACHE_DATA_INT(size, fp); + READ_CACHE_DATA_INT(mtime, fp); + READ_CACHE_DATA_INT(flags.tmp_flags, fp); + READ_CACHE_DATA_INT(flags.perm_flags, fp); + READ_CACHE_DATA_INT(matched, fp); + + if (folder) { + sinfo = g_new(SearchCacheInfo, 1); + sinfo->folder = folder; + sinfo->msgnum = msgnum; + sinfo->size = size; + sinfo->mtime = mtime; + sinfo->flags = flags; + g_hash_table_insert(table, sinfo, + GINT_TO_POINTER(matched)); + ++count; + } + } + } + + debug_print("%d cache items read.\n", count); + + fclose(fp); + return table; +} + +static void virtual_write_search_cache(FILE *fp, FolderItem *item, + MsgInfo *msginfo, gint matched) +{ + if (!item && !msginfo) { + WRITE_CACHE_DATA_INT(0, fp); + return; + } + + if (item) { + gchar *id; + + id = folder_item_get_identifier(item); + if (id) { + WRITE_CACHE_DATA(id, fp); + g_free(id); + } + } + + if (msginfo) { + WRITE_CACHE_DATA_INT(msginfo->msgnum, fp); + WRITE_CACHE_DATA_INT(msginfo->size, fp); + WRITE_CACHE_DATA_INT(msginfo->mtime, fp); + WRITE_CACHE_DATA_INT + ((msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK), fp); + WRITE_CACHE_DATA_INT(msginfo->flags.perm_flags, fp); + WRITE_CACHE_DATA_INT(matched, fp); + } +} + +static void search_cache_free_func(gpointer key, gpointer value, gpointer data) +{ + g_free(key); +} + +static void virtual_search_cache_free(GHashTable *table) +{ + if (table) { + g_hash_table_foreach(table, search_cache_free_func, NULL); + g_hash_table_destroy(table); + } +} + +static GSList *virtual_search_folder(VirtualSearchInfo *info, FolderItem *item) +{ + GSList *match_list = NULL; + GSList *mlist; + GSList *cur; + FilterInfo fltinfo; + gint count = 1, total, ncachehit = 0; + GTimeVal tv_prev, tv_cur; + + g_return_val_if_fail(info != NULL, NULL); + g_return_val_if_fail(info->rule != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->path != NULL, NULL); + + /* prevent circular reference */ + if (item->stype == F_VIRTUAL) + return NULL; + + g_get_current_time(&tv_prev); + status_print(_("Searching %s ..."), item->path); + + mlist = folder_item_get_msg_list(item, TRUE); + total = g_slist_length(mlist); + + memset(&fltinfo, 0, sizeof(FilterInfo)); + + debug_print("start query search: %s\n", item->path); + + virtual_write_search_cache(info->fp, item, NULL, 0); + + for (cur = mlist; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + GSList *hlist; + + g_get_current_time(&tv_cur); + if (tv_cur.tv_sec > tv_prev.tv_sec || + tv_cur.tv_usec - tv_prev.tv_usec > + PROGRESS_UPDATE_INTERVAL * 1000) { + status_print(_("Searching %s (%d / %d)..."), + item->path, count, total); + tv_prev = tv_cur; + } + ++count; + + if (info->search_cache_table) { + gint matched; + SearchCacheInfo sinfo; + + sinfo.folder = item; + sinfo.msgnum = msginfo->msgnum; + sinfo.size = msginfo->size; + sinfo.mtime = msginfo->mtime; + sinfo.flags = msginfo->flags; + + matched = (gint)g_hash_table_lookup + (info->search_cache_table, &sinfo); + if (matched == SCACHE_MATCHED) { + match_list = g_slist_prepend + (match_list, msginfo); + cur->data = NULL; + virtual_write_search_cache(info->fp, NULL, + msginfo, matched); + ++ncachehit; + continue; + } else if (matched == SCACHE_NOT_MATCHED) { + virtual_write_search_cache(info->fp, NULL, + msginfo, matched); + ++ncachehit; + continue; + } + } + + fltinfo.flags = msginfo->flags; + if (info->requires_full_headers) { + gchar *file; + + file = procmsg_get_message_file(msginfo); + hlist = procheader_get_header_list_from_file(file); + g_free(file); + } else + hlist = procheader_get_header_list_from_msginfo + (msginfo); + if (!hlist) + continue; + + if (filter_match_rule(info->rule, msginfo, hlist, &fltinfo)) { + match_list = g_slist_prepend(match_list, msginfo); + cur->data = NULL; + virtual_write_search_cache(info->fp, NULL, msginfo, + SCACHE_MATCHED); + } else { + virtual_write_search_cache(info->fp, NULL, msginfo, + SCACHE_NOT_MATCHED); + } + + procheader_header_list_destroy(hlist); + } + + debug_print("%d cache hits (%d total)\n", ncachehit, total); + + virtual_write_search_cache(info->fp, NULL, NULL, 0); + procmsg_msg_list_free(mlist); + + return g_slist_reverse(match_list); +} + +static gboolean virtual_search_recursive_func(GNode *node, gpointer data) +{ + VirtualSearchInfo *info = (VirtualSearchInfo *)data; + FolderItem *item; + GSList *mlist; + + g_return_val_if_fail(node->data != NULL, FALSE); + + item = FOLDER_ITEM(node->data); + + if (!item->path) + return FALSE; + if (info->exclude_trash && item->stype == F_TRASH) + return FALSE; + + mlist = virtual_search_folder(info, item); + info->mlist = g_slist_concat(info->mlist, mlist); + + return FALSE; +} + +static GSList *virtual_get_msg_list(Folder *folder, FolderItem *item, + gboolean use_cache) +{ + GSList *mlist = NULL; + GSList *flist; + GSList *cur; + FilterRule *rule; + gchar *path; + gchar *rule_file; + gchar *cache_file; + FolderItem *target; + gint new = 0, unread = 0, total = 0; + VirtualSearchInfo info; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->stype == F_VIRTUAL, NULL); + + path = folder_item_get_path(item); + rule_file = g_strconcat(path, G_DIR_SEPARATOR_S, "filter.xml", NULL); + flist = filter_read_file(rule_file); + g_free(rule_file); + + g_free(path); + + if (!flist) { + g_warning("filter rule not found\n"); + return NULL; + } + + rule = (FilterRule *)flist->data; + target = folder_find_item_from_identifier(rule->target_folder); + + if (!target || target == item) { + g_warning("invalid target folder\n"); + goto finish; + } + + info.rule = rule; + info.mlist = NULL; + if (use_cache) + info.search_cache_table = virtual_read_search_cache(item); + else + info.search_cache_table = NULL; + + path = folder_item_get_path(item); + cache_file = g_strconcat(path, G_DIR_SEPARATOR_S, SEARCH_CACHE, NULL); + info.fp = procmsg_open_data_file(cache_file, SEARCH_CACHE_VERSION, + DATA_WRITE, NULL, 0); + g_free(cache_file); + g_free(path); + if (!info.fp) + goto finish; + + info.requires_full_headers = + filter_rule_requires_full_headers(rule); + + if (rule->recursive) { + if (target->stype == F_TRASH) + info.exclude_trash = FALSE; + else + info.exclude_trash = TRUE; + } else + info.exclude_trash = FALSE; + + if (rule->recursive) { + g_node_traverse(target->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + virtual_search_recursive_func, &info); + mlist = info.mlist; + } else + mlist = virtual_search_folder(&info, target); + + fclose(info.fp); + virtual_search_cache_free(info.search_cache_table); + + for (cur = mlist; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + + if (MSG_IS_NEW(msginfo->flags)) + ++new; + if (MSG_IS_UNREAD(msginfo->flags)) + ++unread; + ++total; + } + + item->new = new; + item->unread = unread; + item->total = total; + item->updated = TRUE; + +finish: + filter_rule_list_free(flist); + return mlist; +} + +static gchar *virtual_fetch_msg(Folder *folder, FolderItem *item, gint num) +{ + return NULL; +} + +static MsgInfo *virtual_get_msginfo(Folder *folder, FolderItem *item, gint num) +{ + return NULL; +} + +static gint virtual_close(Folder *folder, FolderItem *item) +{ + return 0; +} + +static gint virtual_scan_folder(Folder *folder, FolderItem *item) +{ + return 0; +} + +static gint virtual_rename_folder(Folder *folder, FolderItem *item, + const gchar *name) +{ + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->stype == F_VIRTUAL, -1); + + return mh_get_class()->rename_folder(folder, item, name); +} + +static gint virtual_move_folder(Folder *folder, FolderItem *item, + FolderItem *new_parent) +{ + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->stype == F_VIRTUAL, -1); + + return mh_get_class()->move_folder(folder, item, new_parent); +} + +static gint virtual_remove_folder(Folder *folder, FolderItem *item) +{ + gchar *path; + + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->stype == F_VIRTUAL, -1); + + path = folder_item_get_path(item); + if (remove_dir_recursive(path) < 0) { + g_warning("can't remove directory '%s'\n", path); + g_free(path); + return -1; + } + + g_free(path); + folder_item_remove(item); + return 0; +} diff --git a/libsylph/virtual.h b/libsylph/virtual.h new file mode 100644 index 0000000..69647ee --- /dev/null +++ b/libsylph/virtual.h @@ -0,0 +1,38 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2007 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __VIRTUAL_H__ +#define __VIRTUAL_H__ + +#include + +#include "folder.h" + +typedef struct _VirtualFolder VirtualFolder; + +#define VIRTUAL_FOLDER(obj) ((VirtualFolder *)obj) + +struct _VirtualFolder +{ + LocalFolder lfolder; +}; + +FolderClass *virtual_get_class (void); + +#endif /* __VIRTUAL_H__ */ diff --git a/libsylph/xml.c b/libsylph/xml.c new file mode 100644 index 0000000..e641e84 --- /dev/null +++ b/libsylph/xml.c @@ -0,0 +1,688 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include + +#include "xml.h" +#include "utils.h" +#include "codeconv.h" + +#define SPARSE_MEMORY +/* if this is defined all attr.names and tag.names are stored + * in a hash table */ +#if defined(SPARSE_MEMORY) +#include "stringtable.h" + +static StringTable *xml_string_table; + +static void xml_string_table_create(void) +{ + if (xml_string_table == NULL) + xml_string_table = string_table_new(); +} +#define XML_STRING_ADD(str) \ + string_table_insert_string(xml_string_table, (str)) +#define XML_STRING_FREE(str) \ + string_table_free_string(xml_string_table, (str)) + +#define XML_STRING_TABLE_CREATE() \ + xml_string_table_create() + +#else /* !SPARSE_MEMORY */ + +#define XML_STRING_ADD(str) \ + g_strdup(str) +#define XML_STRING_FREE(str) \ + g_free(str) + +#define XML_STRING_TABLE_CREATE() + +#endif /* SPARSE_MEMORY */ + +static void xml_free_tag (XMLTag *tag); +static gint xml_get_parenthesis (XMLFile *file, + gchar *buf, + gint len); + +XMLFile *xml_open_file(const gchar *path) +{ + XMLFile *newfile; + + g_return_val_if_fail(path != NULL, NULL); + + XML_STRING_TABLE_CREATE(); + + newfile = g_new(XMLFile, 1); + + newfile->fp = g_fopen(path, "rb"); + if (!newfile->fp) { + g_free(newfile); + return NULL; + } + + newfile->buf = g_string_new(NULL); + newfile->bufp = newfile->buf->str; + + newfile->dtd = NULL; + newfile->encoding = NULL; + newfile->tag_stack = NULL; + newfile->level = 0; + newfile->is_empty_element = FALSE; + + return newfile; +} + +void xml_close_file(XMLFile *file) +{ + g_return_if_fail(file != NULL); + + if (file->fp) fclose(file->fp); + + g_string_free(file->buf, TRUE); + + g_free(file->dtd); + g_free(file->encoding); + + while (file->tag_stack != NULL) + xml_pop_tag(file); + + g_free(file); +} + +static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level) +{ + GNode *node = NULL; + XMLNode *xmlnode; + XMLTag *tag; + + while (xml_parse_next_tag(file) == 0) { + if (file->level < level) break; + if (file->level == level) { + g_warning("xml_build_tree(): Parse error\n"); + break; + } + + tag = xml_get_current_tag(file); + if (!tag) break; + xmlnode = xml_node_new(xml_copy_tag(tag), NULL); + xmlnode->element = xml_get_element(file); + if (!parent) + node = g_node_new(xmlnode); + else + node = g_node_append_data(parent, xmlnode); + + xml_build_tree(file, node, file->level); + if (file->level == 0) break; + } + + return node; +} + +GNode *xml_parse_file(const gchar *path) +{ + XMLFile *file; + GNode *node; + + file = xml_open_file(path); + g_return_val_if_fail(file != NULL, NULL); + + xml_get_dtd(file); + + node = xml_build_tree(file, NULL, file->level); + + xml_close_file(file); + +#if defined(SPARSE_MEMORY) + if (get_debug_mode()) + string_table_get_stats(xml_string_table); +#endif + + return node; +} + +gint xml_get_dtd(XMLFile *file) +{ + gchar buf[XMLBUFSIZE]; + gchar *bufp = buf; + + if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1; + + if ((*bufp++ == '?') && + (bufp = strcasestr(bufp, "xml")) && + (bufp = strcasestr(bufp + 3, "version")) && + (bufp = strchr(bufp + 7, '?'))) { + file->dtd = g_strdup(buf); + if ((bufp = strcasestr(buf, "encoding=\""))) { + bufp += 9; + extract_quote(bufp, '"'); + file->encoding = g_strdup(bufp); + } else + file->encoding = g_strdup(CS_INTERNAL); + } else { + g_warning("Can't get xml dtd\n"); + return -1; + } + + return 0; +} + +gint xml_parse_next_tag(XMLFile *file) +{ + gchar buf[XMLBUFSIZE]; + gchar *bufp = buf; + gchar *tag_str; + XMLTag *tag; + gint len; + + if (file->is_empty_element == TRUE) { + file->is_empty_element = FALSE; + xml_pop_tag(file); + return 0; + } + + if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) { + g_warning("xml_parse_next_tag(): Can't parse next tag\n"); + return -1; + } + + /* end-tag */ + if (buf[0] == '/') { + if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) { + g_warning("xml_parse_next_tag(): Tag name mismatch: %s\n", buf); + return -1; + } + xml_pop_tag(file); + return 0; + } + + tag = xml_tag_new(NULL); + xml_push_tag(file, tag); + + len = strlen(buf); + if (len > 0 && buf[len - 1] == '/') { + file->is_empty_element = TRUE; + buf[len - 1] = '\0'; + g_strchomp(buf); + } + if (strlen(buf) == 0) { + g_warning("xml_parse_next_tag(): Tag name is empty\n"); + return -1; + } + + while (*bufp != '\0' && !g_ascii_isspace(*bufp)) bufp++; + if (*bufp == '\0') { + tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL); + if (tag_str) { + tag->tag = XML_STRING_ADD(tag_str); + g_free(tag_str); + } else + tag->tag = XML_STRING_ADD(buf); + return 0; + } else { + *bufp++ = '\0'; + tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL); + if (tag_str) { + tag->tag = XML_STRING_ADD(tag_str); + g_free(tag_str); + } else + tag->tag = XML_STRING_ADD(buf); + } + + /* parse attributes ( name=value ) */ + while (*bufp) { + XMLAttr *attr; + gchar *attr_name; + gchar *attr_value; + gchar *utf8_attr_name; + gchar *utf8_attr_value; + gchar *p; + gchar quote; + + while (g_ascii_isspace(*bufp)) bufp++; + attr_name = bufp; + if ((p = strchr(attr_name, '=')) == NULL) { + g_warning("xml_parse_next_tag(): Syntax error in tag\n"); + return -1; + } + bufp = p; + *bufp++ = '\0'; + while (g_ascii_isspace(*bufp)) bufp++; + + if (*bufp != '"' && *bufp != '\'') { + g_warning("xml_parse_next_tag(): Syntax error in tag\n"); + return -1; + } + quote = *bufp; + bufp++; + attr_value = bufp; + if ((p = strchr(attr_value, quote)) == NULL) { + g_warning("xml_parse_next_tag(): Syntax error in tag\n"); + return -1; + } + bufp = p; + *bufp++ = '\0'; + + g_strchomp(attr_name); + xml_unescape_str(attr_value); + utf8_attr_name = conv_codeset_strdup + (attr_name, file->encoding, CS_INTERNAL); + utf8_attr_value = conv_codeset_strdup + (attr_value, file->encoding, CS_INTERNAL); + if (!utf8_attr_name) + utf8_attr_name = g_strdup(attr_name); + if (!utf8_attr_value) + utf8_attr_value = g_strdup(attr_value); + + attr = xml_attr_new(utf8_attr_name, utf8_attr_value); + xml_tag_add_attr(tag, attr); + + g_free(utf8_attr_value); + g_free(utf8_attr_name); + } + + return 0; +} + +void xml_push_tag(XMLFile *file, XMLTag *tag) +{ + g_return_if_fail(tag != NULL); + + file->tag_stack = g_list_prepend(file->tag_stack, tag); + file->level++; +} + +void xml_pop_tag(XMLFile *file) +{ + XMLTag *tag; + + if (!file->tag_stack) return; + + tag = (XMLTag *)file->tag_stack->data; + + xml_free_tag(tag); + file->tag_stack = g_list_remove(file->tag_stack, tag); + file->level--; +} + +XMLTag *xml_get_current_tag(XMLFile *file) +{ + if (file->tag_stack) + return (XMLTag *)file->tag_stack->data; + else + return NULL; +} + +GList *xml_get_current_tag_attr(XMLFile *file) +{ + XMLTag *tag; + + tag = xml_get_current_tag(file); + if (!tag) return NULL; + + return tag->attr; +} + +gchar *xml_get_element(XMLFile *file) +{ + gchar *str; + gchar *new_str; + gchar *end; + + while ((end = strchr(file->bufp, '<')) == NULL) + if (xml_read_line(file) < 0) return NULL; + + if (end == file->bufp) + return NULL; + + str = g_strndup(file->bufp, end - file->bufp); + /* this is not XML1.0 strict */ + g_strstrip(str); + xml_unescape_str(str); + + file->bufp = end; + xml_truncate_buf(file); + + if (str[0] == '\0') { + g_free(str); + return NULL; + } + + new_str = conv_codeset_strdup(str, file->encoding, CS_INTERNAL); + if (!new_str) + new_str = g_strdup(str); + g_free(str); + + return new_str; +} + +gint xml_read_line(XMLFile *file) +{ + gchar buf[XMLBUFSIZE]; + gint index; + + if (fgets(buf, sizeof(buf), file->fp) == NULL) + return -1; + + index = file->bufp - file->buf->str; + + g_string_append(file->buf, buf); + + file->bufp = file->buf->str + index; + + return 0; +} + +void xml_truncate_buf(XMLFile *file) +{ + gint len; + + len = file->bufp - file->buf->str; + if (len > 0) { + g_string_erase(file->buf, 0, len); + file->bufp = file->buf->str; + } +} + +gboolean xml_compare_tag(XMLFile *file, const gchar *name) +{ + XMLTag *tag; + + tag = xml_get_current_tag(file); + + if (tag && strcmp(tag->tag, name) == 0) + return TRUE; + else + return FALSE; +} + +XMLNode *xml_node_new(XMLTag *tag, const gchar *text) +{ + XMLNode *node; + + node = g_new(XMLNode, 1); + node->tag = tag; + node->element = g_strdup(text); + + return node; +} + +XMLTag *xml_tag_new(const gchar *tag) +{ + XMLTag *new_tag; + + new_tag = g_new(XMLTag, 1); + if (tag) + new_tag->tag = XML_STRING_ADD(tag); + else + new_tag->tag = NULL; + new_tag->attr = NULL; + + return new_tag; +} + +XMLAttr *xml_attr_new(const gchar *name, const gchar *value) +{ + XMLAttr *new_attr; + + new_attr = g_new(XMLAttr, 1); + new_attr->name = XML_STRING_ADD(name); + new_attr->value = g_strdup(value); + + return new_attr; +} + +void xml_tag_add_attr(XMLTag *tag, XMLAttr *attr) +{ + tag->attr = g_list_append(tag->attr, attr); +} + +XMLTag *xml_copy_tag(XMLTag *tag) +{ + XMLTag *new_tag; + XMLAttr *attr; + GList *list; + + new_tag = xml_tag_new(tag->tag); + for (list = tag->attr; list != NULL; list = list->next) { + attr = xml_copy_attr((XMLAttr *)list->data); + xml_tag_add_attr(new_tag, attr); + } + + return new_tag; +} + +XMLAttr *xml_copy_attr(XMLAttr *attr) +{ + return xml_attr_new(attr->name, attr->value); +} + +gint xml_unescape_str(gchar *str) +{ + gchar *start; + gchar *end; + gchar *p = str; + gchar ch; + gint len; + + while ((start = strchr(p, '&')) != NULL) { + if ((end = strchr(start + 1, ';')) == NULL) { + g_warning("Unescaped `&' appeared\n"); + p = start + 1; + continue; + } + len = end - start + 1; + if (len < 3) { + p = end + 1; + continue; + } + + if (!strncmp(start, "<", 4)) + ch = '<'; + else if (!strncmp(start, ">", 4)) + ch = '>'; + else if (!strncmp(start, "&", 5)) + ch = '&'; + else if (!strncmp(start, "'", 6)) + ch = '\''; + else if (!strncmp(start, """, 6)) + ch = '\"'; + else { + p = end + 1; + continue; + } + + *start = ch; + memmove(start + 1, end + 1, strlen(end + 1) + 1); + p = start + 1; + } + + return 0; +} + +gchar *xml_escape_str(const gchar *str) +{ + GString *estr; + const gchar *p; + + if (!str) + return NULL; + + estr = g_string_sized_new(strlen(str)); + for (p = str; *p != '\0'; p++) { + switch (*p) { + case '<': + g_string_append(estr, "<"); + break; + case '>': + g_string_append(estr, ">"); + break; + case '&': + g_string_append(estr, "&"); + break; + case '\'': + g_string_append(estr, "'"); + break; + case '\"': + g_string_append(estr, """); + break; + default: + g_string_append_c(estr, *p); + break; + } + } + + return g_string_free(estr, FALSE); +} + +gint xml_file_put_escape_str(FILE *fp, const gchar *str) +{ + const gchar *p; + + g_return_val_if_fail(fp != NULL, -1); + + if (!str) return 0; + + for (p = str; *p != '\0'; p++) { + switch (*p) { + case '<': + fputs("<", fp); + break; + case '>': + fputs(">", fp); + break; + case '&': + fputs("&", fp); + break; + case '\'': + fputs("'", fp); + break; + case '\"': + fputs(""", fp); + break; + default: + fputc(*p, fp); + } + } + + return 0; +} + +gint xml_file_put_xml_decl(FILE *fp) +{ + g_return_val_if_fail(fp != NULL, -1); + + fprintf(fp, "\n", CS_INTERNAL); + return 0; +} + +gint xml_file_put_node(FILE *fp, XMLNode *node) +{ + GList *cur; + + g_return_val_if_fail(fp != NULL, -1); + g_return_val_if_fail(node != NULL, -1); + + fprintf(fp, "<%s", node->tag->tag); + + for (cur = node->tag->attr; cur != NULL; cur = cur->next) { + XMLAttr *attr = (XMLAttr *)cur->data; + fprintf(fp, " %s=\"", attr->name); + xml_file_put_escape_str(fp, attr->value); + fputs("\"", fp); + } + + if (node->element) { + fputs(">", fp); + xml_file_put_escape_str(fp, node->element); + fprintf(fp, "\n", node->tag->tag); + } else { + fputs(" />\n", fp); + } + + return 0; +} + +void xml_free_node(XMLNode *node) +{ + if (!node) return; + + xml_free_tag(node->tag); + g_free(node->element); + g_free(node); +} + +static gboolean xml_free_func(GNode *node, gpointer data) +{ + XMLNode *xmlnode = node->data; + + xml_free_node(xmlnode); + return FALSE; +} + +void xml_free_tree(GNode *node) +{ + g_return_if_fail(node != NULL); + + g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func, + NULL); + + g_node_destroy(node); +} + +static void xml_free_tag(XMLTag *tag) +{ + if (!tag) return; + + XML_STRING_FREE(tag->tag); + while (tag->attr != NULL) { + XMLAttr *attr = (XMLAttr *)tag->attr->data; + XML_STRING_FREE(attr->name); + g_free(attr->value); + g_free(attr); + tag->attr = g_list_remove(tag->attr, tag->attr->data); + } + g_free(tag); +} + +static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len) +{ + gchar *start; + gchar *end; + + buf[0] = '\0'; + + while ((start = strchr(file->bufp, '<')) == NULL) + if (xml_read_line(file) < 0) return -1; + + start++; + file->bufp = start; + + while ((end = strchr(file->bufp, '>')) == NULL) + if (xml_read_line(file) < 0) return -1; + + strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len)); + g_strstrip(buf); + file->bufp = end + 1; + xml_truncate_buf(file); + + return 0; +} diff --git a/libsylph/xml.h b/libsylph/xml.h new file mode 100644 index 0000000..766281b --- /dev/null +++ b/libsylph/xml.h @@ -0,0 +1,109 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2005 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __XML_H__ +#define __XML_H__ + +#include +#include + +#define XMLBUFSIZE 8192 + +typedef struct _XMLAttr XMLAttr; +typedef struct _XMLTag XMLTag; +typedef struct _XMLNode XMLNode; +typedef struct _XMLFile XMLFile; + +struct _XMLAttr +{ + gchar *name; + gchar *value; +}; + +struct _XMLTag +{ + gchar *tag; + GList *attr; +}; + +struct _XMLNode +{ + XMLTag *tag; + gchar *element; +}; + +struct _XMLFile +{ + FILE *fp; + + GString *buf; + gchar *bufp; + + gchar *dtd; + gchar *encoding; + + GList *tag_stack; + guint level; + + gboolean is_empty_element; +}; + +XMLFile *xml_open_file (const gchar *path); +void xml_close_file (XMLFile *file); +GNode *xml_parse_file (const gchar *path); + +gint xml_get_dtd (XMLFile *file); +gint xml_parse_next_tag (XMLFile *file); +void xml_push_tag (XMLFile *file, + XMLTag *tag); +void xml_pop_tag (XMLFile *file); + +XMLTag *xml_get_current_tag (XMLFile *file); +GList *xml_get_current_tag_attr(XMLFile *file); +gchar *xml_get_element (XMLFile *file); + +gint xml_read_line (XMLFile *file); +void xml_truncate_buf (XMLFile *file); +gboolean xml_compare_tag (XMLFile *file, + const gchar *name); + +XMLNode *xml_node_new (XMLTag *tag, + const gchar *text); +XMLTag *xml_tag_new (const gchar *tag); +XMLAttr *xml_attr_new (const gchar *name, + const gchar *value); +void xml_tag_add_attr (XMLTag *tag, + XMLAttr *attr); + +XMLTag *xml_copy_tag (XMLTag *tag); +XMLAttr *xml_copy_attr (XMLAttr *attr); + +gint xml_unescape_str (gchar *str); +gchar *xml_escape_str (const gchar *str); +gint xml_file_put_escape_str (FILE *fp, + const gchar *str); + +gint xml_file_put_xml_decl (FILE *fp); +gint xml_file_put_node (FILE *fp, + XMLNode *node); + +void xml_free_node (XMLNode *node); +void xml_free_tree (GNode *node); + +#endif /* __XML_H__ */ -- cgit v1.3