summaryrefslogtreecommitdiff
path: root/libsylph
diff options
context:
space:
mode:
authorSimeon Simeonov2018-02-26 11:23:00 +0100
committerSimeon Simeonov2018-02-26 11:23:00 +0100
commit0b3cbf57875fd692e4ba0b336fefa4bee1ed00dc (patch)
treeaf916a30553c78ce9d4f2d8658656a175c5b6921 /libsylph
Initial commit for sylpheed 3.7.0
Diffstat (limited to 'libsylph')
-rw-r--r--libsylph/Makefile.am118
-rw-r--r--libsylph/Makefile.in881
-rw-r--r--libsylph/account.c491
-rw-r--r--libsylph/account.h70
-rw-r--r--libsylph/base64.c168
-rw-r--r--libsylph/base64.h46
-rw-r--r--libsylph/codeconv.c2856
-rw-r--r--libsylph/codeconv.h267
-rw-r--r--libsylph/customheader.c233
-rw-r--r--libsylph/customheader.h45
-rw-r--r--libsylph/defs.h134
-rw-r--r--libsylph/displayheader.c59
-rw-r--r--libsylph/displayheader.h37
-rw-r--r--libsylph/enums.h66
-rw-r--r--libsylph/filter.c1951
-rw-r--r--libsylph/filter.h247
-rw-r--r--libsylph/folder.c1994
-rw-r--r--libsylph/folder.h475
-rw-r--r--libsylph/html.c791
-rw-r--r--libsylph/html.h89
-rw-r--r--libsylph/imap.c5215
-rw-r--r--libsylph/imap.h130
-rw-r--r--libsylph/libsylph-0.def714
-rw-r--r--libsylph/mbox.c572
-rw-r--r--libsylph/mbox.h56
-rw-r--r--libsylph/md5.c637
-rw-r--r--libsylph/md5.h61
-rw-r--r--libsylph/md5_hmac.c134
-rw-r--r--libsylph/md5_hmac.h33
-rw-r--r--libsylph/mh.c1898
-rw-r--r--libsylph/mh.h38
-rw-r--r--libsylph/news.c1110
-rw-r--r--libsylph/news.h60
-rw-r--r--libsylph/nntp.c471
-rw-r--r--libsylph/nntp.h123
-rw-r--r--libsylph/pop.c927
-rw-r--r--libsylph/pop.h157
-rw-r--r--libsylph/prefs.c588
-rw-r--r--libsylph/prefs.h80
-rw-r--r--libsylph/prefs_account.c296
-rw-r--r--libsylph/prefs_account.h205
-rw-r--r--libsylph/prefs_common.c634
-rw-r--r--libsylph/prefs_common.h375
-rw-r--r--libsylph/procheader.c979
-rw-r--r--libsylph/procheader.h101
-rw-r--r--libsylph/procmime.c1955
-rw-r--r--libsylph/procmime.h213
-rw-r--r--libsylph/procmsg.c2446
-rw-r--r--libsylph/procmsg.h355
-rw-r--r--libsylph/quoted-printable.c232
-rw-r--r--libsylph/quoted-printable.h36
-rw-r--r--libsylph/recv.c245
-rw-r--r--libsylph/recv.h49
-rw-r--r--libsylph/session.c1320
-rw-r--r--libsylph/session.h248
-rw-r--r--libsylph/smtp.c617
-rw-r--r--libsylph/smtp.h116
-rw-r--r--libsylph/socket.c2179
-rw-r--r--libsylph/socket.h163
-rw-r--r--libsylph/socks.c267
-rw-r--r--libsylph/socks.h59
-rw-r--r--libsylph/ssl.c405
-rw-r--r--libsylph/ssl.h65
-rw-r--r--libsylph/ssl_hostname_validation.c282
-rw-r--r--libsylph/ssl_hostname_validation.h66
-rw-r--r--libsylph/stringtable.c163
-rw-r--r--libsylph/stringtable.h38
-rw-r--r--libsylph/syl-marshal.c131
-rw-r--r--libsylph/syl-marshal.h34
-rw-r--r--libsylph/syl-marshal.list4
-rw-r--r--libsylph/sylmain.c342
-rw-r--r--libsylph/sylmain.h57
-rw-r--r--libsylph/unmime.c135
-rw-r--r--libsylph/unmime.h27
-rw-r--r--libsylph/utils.c4849
-rw-r--r--libsylph/utils.h599
-rw-r--r--libsylph/uuencode.c101
-rw-r--r--libsylph/uuencode.h24
-rw-r--r--libsylph/virtual.c603
-rw-r--r--libsylph/virtual.h38
-rw-r--r--libsylph/xml.c688
-rw-r--r--libsylph/xml.h109
82 files changed, 45872 insertions, 0 deletions
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 @@
1
2AM_CPPFLAGS = \
3 -DG_LOG_DOMAIN=\"LibSylph\" \
4 -DSYSCONFDIR=\""$(sysconfdir)"\" \
5 -DLOCALEDIR=\""$(localedir)"\"
6
7INCLUDES = -I$(top_srcdir) $(GLIB_CFLAGS) $(LIBSYLPH_CFLAGS)
8
9lib_LTLIBRARIES = libsylph-0.la
10#noinst_LTLIBRARIES = libsylph.la
11
12libsylph_0_la_SOURCES = \
13 account.c \
14 base64.c \
15 codeconv.c \
16 customheader.c \
17 displayheader.c \
18 filter.c \
19 folder.c \
20 html.c \
21 imap.c \
22 mbox.c \
23 md5.c \
24 md5_hmac.c \
25 mh.c \
26 news.c \
27 nntp.c \
28 pop.c \
29 prefs.c \
30 prefs_account.c \
31 prefs_common.c \
32 procheader.c \
33 procmime.c \
34 procmsg.c \
35 quoted-printable.c \
36 recv.c \
37 session.c \
38 smtp.c \
39 socket.c \
40 socks.c \
41 ssl.c \
42 ssl_hostname_validation.c \
43 stringtable.c \
44 sylmain.c \
45 unmime.c \
46 utils.c \
47 uuencode.c \
48 virtual.c \
49 xml.c \
50 syl-marshal.c
51
52libsylph_0includedir=$(includedir)/sylpheed/sylph
53libsylph_0include_HEADERS = \
54 defs.h \
55 enums.h \
56 account.h \
57 base64.h \
58 codeconv.h \
59 customheader.h \
60 displayheader.h \
61 filter.h \
62 folder.h \
63 html.h \
64 imap.h \
65 mbox.h \
66 md5.h \
67 md5_hmac.h \
68 mh.h \
69 news.h \
70 nntp.h \
71 pop.h \
72 prefs.h \
73 prefs_account.h \
74 prefs_common.h \
75 procheader.h \
76 procmime.h \
77 procmsg.h \
78 quoted-printable.h \
79 recv.h \
80 session.h \
81 smtp.h \
82 socket.h \
83 socks.h \
84 ssl.h \
85 ssl_hostname_validation.h \
86 stringtable.h \
87 sylmain.h \
88 unmime.h \
89 utils.h \
90 uuencode.h \
91 virtual.h \
92 xml.h \
93 syl-marshal.h
94
95BUILT_SOURCES = \
96 syl-marshal.c \
97 syl-marshal.h
98
99EXTRA_DIST = \
100 syl-marshal.list \
101 libsylph-0.def
102
103if NATIVE_WIN32
104no_undefined = -no-undefined
105export_symbols = -export-symbols libsylph-0.def
106endif
107
108libsylph_0_la_LDFLAGS = \
109 -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \
110 -export-dynamic $(no_undefined) $(export_symbols)
111
112libsylph_0_la_LIBADD = $(GLIB_LIBS) $(LIBICONV) $(LIBSYLPH_LIBS)
113
114syl-marshal.h: syl-marshal.list
115 $(GLIB_GENMARSHAL) $< --header --prefix=syl_marshal > $@
116
117syl-marshal.c: syl-marshal.list
118 $(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 @@
1# Makefile.in generated by automake 1.14.1 from Makefile.am.
2# @configure_input@
3
4# Copyright (C) 1994-2013 Free Software Foundation, Inc.
5
6# This Makefile.in is free software; the Free Software Foundation
7# gives unlimited permission to copy and/or distribute it,
8# with or without modifications, as long as this notice is preserved.
9
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
12# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13# PARTICULAR PURPOSE.
14
15@SET_MAKE@
16
17
18VPATH = @srcdir@
19am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
20am__make_running_with_option = \
21 case $${target_option-} in \
22 ?) ;; \
23 *) echo "am__make_running_with_option: internal error: invalid" \
24 "target option '$${target_option-}' specified" >&2; \
25 exit 1;; \
26 esac; \
27 has_opt=no; \
28 sane_makeflags=$$MAKEFLAGS; \
29 if $(am__is_gnu_make); then \
30 sane_makeflags=$$MFLAGS; \
31 else \
32 case $$MAKEFLAGS in \
33 *\\[\ \ ]*) \
34 bs=\\; \
35 sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
36 | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
37 esac; \
38 fi; \
39 skip_next=no; \
40 strip_trailopt () \
41 { \
42 flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
43 }; \
44 for flg in $$sane_makeflags; do \
45 test $$skip_next = yes && { skip_next=no; continue; }; \
46 case $$flg in \
47 *=*|--*) continue;; \
48 -*I) strip_trailopt 'I'; skip_next=yes;; \
49 -*I?*) strip_trailopt 'I';; \
50 -*O) strip_trailopt 'O'; skip_next=yes;; \
51 -*O?*) strip_trailopt 'O';; \
52 -*l) strip_trailopt 'l'; skip_next=yes;; \
53 -*l?*) strip_trailopt 'l';; \
54 -[dEDm]) skip_next=yes;; \
55 -[JT]) skip_next=yes;; \
56 esac; \
57 case $$flg in \
58 *$$target_option*) has_opt=yes; break;; \
59 esac; \
60 done; \
61 test $$has_opt = yes
62am__make_dryrun = (target_option=n; $(am__make_running_with_option))
63am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
64pkgdatadir = $(datadir)/@PACKAGE@
65pkgincludedir = $(includedir)/@PACKAGE@
66pkglibdir = $(libdir)/@PACKAGE@
67pkglibexecdir = $(libexecdir)/@PACKAGE@
68am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
69install_sh_DATA = $(install_sh) -c -m 644
70install_sh_PROGRAM = $(install_sh) -c
71install_sh_SCRIPT = $(install_sh) -c
72INSTALL_HEADER = $(INSTALL_DATA)
73transform = $(program_transform_name)
74NORMAL_INSTALL = :
75PRE_INSTALL = :
76POST_INSTALL = :
77NORMAL_UNINSTALL = :
78PRE_UNINSTALL = :
79POST_UNINSTALL = :
80build_triplet = @build@
81host_triplet = @host@
82target_triplet = @target@
83subdir = libsylph
84DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
85 $(top_srcdir)/mkinstalldirs $(top_srcdir)/depcomp \
86 $(libsylph_0include_HEADERS)
87ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
88am__aclocal_m4_deps = $(top_srcdir)/ac/aclocal-include.m4 \
89 $(top_srcdir)/ac/gnupg-check-typedef.m4 \
90 $(top_srcdir)/configure.ac
91am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
92 $(ACLOCAL_M4)
93mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
94CONFIG_HEADER = $(top_builddir)/config.h
95CONFIG_CLEAN_FILES =
96CONFIG_CLEAN_VPATH_FILES =
97am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
98am__vpath_adj = case $$p in \
99 $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
100 *) f=$$p;; \
101 esac;
102am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
103am__install_max = 40
104am__nobase_strip_setup = \
105 srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
106am__nobase_strip = \
107 for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
108am__nobase_list = $(am__nobase_strip_setup); \
109 for p in $$list; do echo "$$p $$p"; done | \
110 sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
111 $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
112 if (++n[$$2] == $(am__install_max)) \
113 { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
114 END { for (dir in files) print dir, files[dir] }'
115am__base_list = \
116 sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
117 sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
118am__uninstall_files_from_dir = { \
119 test -z "$$files" \
120 || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
121 || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
122 $(am__cd) "$$dir" && rm -f $$files; }; \
123 }
124am__installdirs = "$(DESTDIR)$(libdir)" \
125 "$(DESTDIR)$(libsylph_0includedir)"
126LTLIBRARIES = $(lib_LTLIBRARIES)
127am__DEPENDENCIES_1 =
128libsylph_0_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
129 $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
130am_libsylph_0_la_OBJECTS = account.lo base64.lo codeconv.lo \
131 customheader.lo displayheader.lo filter.lo folder.lo html.lo \
132 imap.lo mbox.lo md5.lo md5_hmac.lo mh.lo news.lo nntp.lo \
133 pop.lo prefs.lo prefs_account.lo prefs_common.lo procheader.lo \
134 procmime.lo procmsg.lo quoted-printable.lo recv.lo session.lo \
135 smtp.lo socket.lo socks.lo ssl.lo ssl_hostname_validation.lo \
136 stringtable.lo sylmain.lo unmime.lo utils.lo uuencode.lo \
137 virtual.lo xml.lo syl-marshal.lo
138libsylph_0_la_OBJECTS = $(am_libsylph_0_la_OBJECTS)
139AM_V_lt = $(am__v_lt_@AM_V@)
140am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
141am__v_lt_0 = --silent
142am__v_lt_1 =
143libsylph_0_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
144 $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
145 $(libsylph_0_la_LDFLAGS) $(LDFLAGS) -o $@
146AM_V_P = $(am__v_P_@AM_V@)
147am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
148am__v_P_0 = false
149am__v_P_1 = :
150AM_V_GEN = $(am__v_GEN_@AM_V@)
151am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
152am__v_GEN_0 = @echo " GEN " $@;
153am__v_GEN_1 =
154AM_V_at = $(am__v_at_@AM_V@)
155am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
156am__v_at_0 = @
157am__v_at_1 =
158DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
159depcomp = $(SHELL) $(top_srcdir)/depcomp
160am__depfiles_maybe = depfiles
161am__mv = mv -f
162COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
163 $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
164LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
165 $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
166 $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
167 $(AM_CFLAGS) $(CFLAGS)
168AM_V_CC = $(am__v_CC_@AM_V@)
169am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
170am__v_CC_0 = @echo " CC " $@;
171am__v_CC_1 =
172CCLD = $(CC)
173LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
174 $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
175 $(AM_LDFLAGS) $(LDFLAGS) -o $@
176AM_V_CCLD = $(am__v_CCLD_@AM_V@)
177am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
178am__v_CCLD_0 = @echo " CCLD " $@;
179am__v_CCLD_1 =
180SOURCES = $(libsylph_0_la_SOURCES)
181DIST_SOURCES = $(libsylph_0_la_SOURCES)
182am__can_run_installinfo = \
183 case $$AM_UPDATE_INFO_DIR in \
184 n|no|NO) false;; \
185 *) (install-info --version) >/dev/null 2>&1;; \
186 esac
187HEADERS = $(libsylph_0include_HEADERS)
188am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
189# Read a list of newline-separated strings from the standard input,
190# and print each of them once, without duplicates. Input order is
191# *not* preserved.
192am__uniquify_input = $(AWK) '\
193 BEGIN { nonempty = 0; } \
194 { items[$$0] = 1; nonempty = 1; } \
195 END { if (nonempty) { for (i in items) print i; }; } \
196'
197# Make sure the list of sources is unique. This is necessary because,
198# e.g., the same source file might be shared among _SOURCES variables
199# for different programs/libraries.
200am__define_uniq_tagged_files = \
201 list='$(am__tagged_files)'; \
202 unique=`for i in $$list; do \
203 if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
204 done | $(am__uniquify_input)`
205ETAGS = etags
206CTAGS = ctags
207DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
208ACLOCAL = @ACLOCAL@
209ALLOCA = @ALLOCA@
210AMTAR = @AMTAR@
211AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
212AR = @AR@
213AUTOCONF = @AUTOCONF@
214AUTOHEADER = @AUTOHEADER@
215AUTOMAKE = @AUTOMAKE@
216AWK = @AWK@
217BUILD_REVISION = @BUILD_REVISION@
218CATALOGS = @CATALOGS@
219CATOBJEXT = @CATOBJEXT@
220CC = @CC@
221CCDEPMODE = @CCDEPMODE@
222CFLAGS = @CFLAGS@
223CPP = @CPP@
224CPPFLAGS = @CPPFLAGS@
225CYGPATH_W = @CYGPATH_W@
226DATADIRNAME = @DATADIRNAME@
227DEFS = @DEFS@
228DEPDIR = @DEPDIR@
229DEVEL_VERSION = @DEVEL_VERSION@
230DLLTOOL = @DLLTOOL@
231DSYMUTIL = @DSYMUTIL@
232DUMPBIN = @DUMPBIN@
233ECHO_C = @ECHO_C@
234ECHO_N = @ECHO_N@
235ECHO_T = @ECHO_T@
236EGREP = @EGREP@
237EXEEXT = @EXEEXT@
238EXTRA_VERSION = @EXTRA_VERSION@
239FGREP = @FGREP@
240GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
241GLIB_CFLAGS = @GLIB_CFLAGS@
242GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@
243GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
244GLIB_LIBS = @GLIB_LIBS@
245GLIB_MKENUMS = @GLIB_MKENUMS@
246GMOFILES = @GMOFILES@
247GMSGFMT = @GMSGFMT@
248GOBJECT_QUERY = @GOBJECT_QUERY@
249GPGME_CFLAGS = @GPGME_CFLAGS@
250GPGME_CONFIG = @GPGME_CONFIG@
251GPGME_LIBS = @GPGME_LIBS@
252GREP = @GREP@
253GTK_CFLAGS = @GTK_CFLAGS@
254GTK_LIBS = @GTK_LIBS@
255INSTALL = @INSTALL@
256INSTALL_DATA = @INSTALL_DATA@
257INSTALL_PROGRAM = @INSTALL_PROGRAM@
258INSTALL_SCRIPT = @INSTALL_SCRIPT@
259INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
260INSTOBJEXT = @INSTOBJEXT@
261INTLLIBS = @INTLLIBS@
262LD = @LD@
263LDAP_LIBS = @LDAP_LIBS@
264LDFLAGS = @LDFLAGS@
265LEX = @LEX@
266LEXLIB = @LEXLIB@
267LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
268LIBICONV = @LIBICONV@
269LIBOBJS = @LIBOBJS@
270LIBS = @LIBS@
271LIBSYLPH_CFLAGS = @LIBSYLPH_CFLAGS@
272LIBSYLPH_LIBS = @LIBSYLPH_LIBS@
273LIBTOOL = @LIBTOOL@
274LIPO = @LIPO@
275LN_S = @LN_S@
276LTLIBICONV = @LTLIBICONV@
277LTLIBOBJS = @LTLIBOBJS@
278LT_AGE = @LT_AGE@
279LT_CURRENT = @LT_CURRENT@
280LT_RELEASE = @LT_RELEASE@
281LT_REVISION = @LT_REVISION@
282MAINT = @MAINT@
283MAJOR_VERSION = @MAJOR_VERSION@
284MAKEINFO = @MAKEINFO@
285MANIFEST_TOOL = @MANIFEST_TOOL@
286MICRO_VERSION = @MICRO_VERSION@
287MINOR_VERSION = @MINOR_VERSION@
288MKDIR_P = @MKDIR_P@
289MKINSTALLDIRS = @MKINSTALLDIRS@
290MSGFMT = @MSGFMT@
291MSGFMT_OPTS = @MSGFMT_OPTS@
292NM = @NM@
293NMEDIT = @NMEDIT@
294OBJDUMP = @OBJDUMP@
295OBJEXT = @OBJEXT@
296OPENSSL_CFLAGS = @OPENSSL_CFLAGS@
297OPENSSL_LIBS = @OPENSSL_LIBS@
298OTOOL = @OTOOL@
299OTOOL64 = @OTOOL64@
300PACKAGE = @PACKAGE@
301PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
302PACKAGE_NAME = @PACKAGE_NAME@
303PACKAGE_STRING = @PACKAGE_STRING@
304PACKAGE_TARNAME = @PACKAGE_TARNAME@
305PACKAGE_URL = @PACKAGE_URL@
306PACKAGE_VERSION = @PACKAGE_VERSION@
307PATH_SEPARATOR = @PATH_SEPARATOR@
308PKG_CONFIG = @PKG_CONFIG@
309PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
310PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
311POFILES = @POFILES@
312POSUB = @POSUB@
313PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@
314PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@
315RANLIB = @RANLIB@
316SED = @SED@
317SET_MAKE = @SET_MAKE@
318SHELL = @SHELL@
319STRIP = @STRIP@
320SYLPHEED_LIBS = @SYLPHEED_LIBS@
321USE_NLS = @USE_NLS@
322VERSION = @VERSION@
323WINDRES = @WINDRES@
324XGETTEXT = @XGETTEXT@
325YACC = @YACC@
326YFLAGS = @YFLAGS@
327abs_builddir = @abs_builddir@
328abs_srcdir = @abs_srcdir@
329abs_top_builddir = @abs_top_builddir@
330abs_top_srcdir = @abs_top_srcdir@
331ac_ct_AR = @ac_ct_AR@
332ac_ct_CC = @ac_ct_CC@
333ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
334am__include = @am__include@
335am__leading_dot = @am__leading_dot@
336am__quote = @am__quote@
337am__tar = @am__tar@
338am__untar = @am__untar@
339bindir = @bindir@
340build = @build@
341build_alias = @build_alias@
342build_cpu = @build_cpu@
343build_os = @build_os@
344build_vendor = @build_vendor@
345builddir = @builddir@
346datadir = @datadir@
347datarootdir = @datarootdir@
348docdir = @docdir@
349dvidir = @dvidir@
350exec_prefix = @exec_prefix@
351faqdir = @faqdir@
352host = @host@
353host_alias = @host_alias@
354host_cpu = @host_cpu@
355host_os = @host_os@
356host_vendor = @host_vendor@
357htmldir = @htmldir@
358includedir = @includedir@
359infodir = @infodir@
360install_sh = @install_sh@
361libdir = @libdir@
362libexecdir = @libexecdir@
363localedir = @localedir@
364localstatedir = @localstatedir@
365mandir = @mandir@
366manualdir = @manualdir@
367mkdir_p = @mkdir_p@
368oldincludedir = @oldincludedir@
369pdfdir = @pdfdir@
370plugindir = @plugindir@
371prefix = @prefix@
372program_transform_name = @program_transform_name@
373psdir = @psdir@
374sbindir = @sbindir@
375sharedstatedir = @sharedstatedir@
376srcdir = @srcdir@
377sysconfdir = @sysconfdir@
378target = @target@
379target_alias = @target_alias@
380target_cpu = @target_cpu@
381target_os = @target_os@
382target_vendor = @target_vendor@
383themedir = @themedir@
384top_build_prefix = @top_build_prefix@
385top_builddir = @top_builddir@
386top_srcdir = @top_srcdir@
387AM_CPPFLAGS = \
388 -DG_LOG_DOMAIN=\"LibSylph\" \
389 -DSYSCONFDIR=\""$(sysconfdir)"\" \
390 -DLOCALEDIR=\""$(localedir)"\"
391
392INCLUDES = -I$(top_srcdir) $(GLIB_CFLAGS) $(LIBSYLPH_CFLAGS)
393lib_LTLIBRARIES = libsylph-0.la
394#noinst_LTLIBRARIES = libsylph.la
395libsylph_0_la_SOURCES = \
396 account.c \
397 base64.c \
398 codeconv.c \
399 customheader.c \
400 displayheader.c \
401 filter.c \
402 folder.c \
403 html.c \
404 imap.c \
405 mbox.c \
406 md5.c \
407 md5_hmac.c \
408 mh.c \
409 news.c \
410 nntp.c \
411 pop.c \
412 prefs.c \
413 prefs_account.c \
414 prefs_common.c \
415 procheader.c \
416 procmime.c \
417 procmsg.c \
418 quoted-printable.c \
419 recv.c \
420 session.c \
421 smtp.c \
422 socket.c \
423 socks.c \
424 ssl.c \
425 ssl_hostname_validation.c \
426 stringtable.c \
427 sylmain.c \
428 unmime.c \
429 utils.c \
430 uuencode.c \
431 virtual.c \
432 xml.c \
433 syl-marshal.c
434
435libsylph_0includedir = $(includedir)/sylpheed/sylph
436libsylph_0include_HEADERS = \
437 defs.h \
438 enums.h \
439 account.h \
440 base64.h \
441 codeconv.h \
442 customheader.h \
443 displayheader.h \
444 filter.h \
445 folder.h \
446 html.h \
447 imap.h \
448 mbox.h \
449 md5.h \
450 md5_hmac.h \
451 mh.h \
452 news.h \
453 nntp.h \
454 pop.h \
455 prefs.h \
456 prefs_account.h \
457 prefs_common.h \
458 procheader.h \
459 procmime.h \
460 procmsg.h \
461 quoted-printable.h \
462 recv.h \
463 session.h \
464 smtp.h \
465 socket.h \
466 socks.h \
467 ssl.h \
468 ssl_hostname_validation.h \
469 stringtable.h \
470 sylmain.h \
471 unmime.h \
472 utils.h \
473 uuencode.h \
474 virtual.h \
475 xml.h \
476 syl-marshal.h
477
478BUILT_SOURCES = \
479 syl-marshal.c \
480 syl-marshal.h
481
482EXTRA_DIST = \
483 syl-marshal.list \
484 libsylph-0.def
485
486@NATIVE_WIN32_TRUE@no_undefined = -no-undefined
487@NATIVE_WIN32_TRUE@export_symbols = -export-symbols libsylph-0.def
488libsylph_0_la_LDFLAGS = \
489 -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \
490 -export-dynamic $(no_undefined) $(export_symbols)
491
492libsylph_0_la_LIBADD = $(GLIB_LIBS) $(LIBICONV) $(LIBSYLPH_LIBS)
493all: $(BUILT_SOURCES)
494 $(MAKE) $(AM_MAKEFLAGS) all-am
495
496.SUFFIXES:
497.SUFFIXES: .c .lo .o .obj
498$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
499 @for dep in $?; do \
500 case '$(am__configure_deps)' in \
501 *$$dep*) \
502 ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
503 && { if test -f $@; then exit 0; else break; fi; }; \
504 exit 1;; \
505 esac; \
506 done; \
507 echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign libsylph/Makefile'; \
508 $(am__cd) $(top_srcdir) && \
509 $(AUTOMAKE) --foreign libsylph/Makefile
510.PRECIOUS: Makefile
511Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
512 @case '$?' in \
513 *config.status*) \
514 cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
515 *) \
516 echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
517 cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
518 esac;
519
520$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
521 cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
522
523$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
524 cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
525$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
526 cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
527$(am__aclocal_m4_deps):
528
529install-libLTLIBRARIES: $(lib_LTLIBRARIES)
530 @$(NORMAL_INSTALL)
531 @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
532 list2=; for p in $$list; do \
533 if test -f $$p; then \
534 list2="$$list2 $$p"; \
535 else :; fi; \
536 done; \
537 test -z "$$list2" || { \
538 echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
539 $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
540 echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
541 $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
542 }
543
544uninstall-libLTLIBRARIES:
545 @$(NORMAL_UNINSTALL)
546 @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
547 for p in $$list; do \
548 $(am__strip_dir) \
549 echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
550 $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
551 done
552
553clean-libLTLIBRARIES:
554 -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
555 @list='$(lib_LTLIBRARIES)'; \
556 locs=`for p in $$list; do echo $$p; done | \
557 sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
558 sort -u`; \
559 test -z "$$locs" || { \
560 echo rm -f $${locs}; \
561 rm -f $${locs}; \
562 }
563
564libsylph-0.la: $(libsylph_0_la_OBJECTS) $(libsylph_0_la_DEPENDENCIES) $(EXTRA_libsylph_0_la_DEPENDENCIES)
565 $(AM_V_CCLD)$(libsylph_0_la_LINK) -rpath $(libdir) $(libsylph_0_la_OBJECTS) $(libsylph_0_la_LIBADD) $(LIBS)
566
567mostlyclean-compile:
568 -rm -f *.$(OBJEXT)
569
570distclean-compile:
571 -rm -f *.tab.c
572
573@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/account.Plo@am__quote@
574@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base64.Plo@am__quote@
575@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/codeconv.Plo@am__quote@
576@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/customheader.Plo@am__quote@
577@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/displayheader.Plo@am__quote@
578@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filter.Plo@am__quote@
579@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/folder.Plo@am__quote@
580@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/html.Plo@am__quote@
581@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap.Plo@am__quote@
582@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mbox.Plo@am__quote@
583@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Plo@am__quote@
584@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5_hmac.Plo@am__quote@
585@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mh.Plo@am__quote@
586@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/news.Plo@am__quote@
587@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nntp.Plo@am__quote@
588@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pop.Plo@am__quote@
589@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/prefs.Plo@am__quote@
590@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/prefs_account.Plo@am__quote@
591@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/prefs_common.Plo@am__quote@
592@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/procheader.Plo@am__quote@
593@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/procmime.Plo@am__quote@
594@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/procmsg.Plo@am__quote@
595@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quoted-printable.Plo@am__quote@
596@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/recv.Plo@am__quote@
597@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session.Plo@am__quote@
598@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smtp.Plo@am__quote@
599@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket.Plo@am__quote@
600@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socks.Plo@am__quote@
601@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ssl.Plo@am__quote@
602@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ssl_hostname_validation.Plo@am__quote@
603@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stringtable.Plo@am__quote@
604@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/syl-marshal.Plo@am__quote@
605@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sylmain.Plo@am__quote@
606@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unmime.Plo@am__quote@
607@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Plo@am__quote@
608@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/uuencode.Plo@am__quote@
609@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual.Plo@am__quote@
610@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xml.Plo@am__quote@
611
612.c.o:
613@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
614@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
615@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
616@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
617@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
618
619.c.obj:
620@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
621@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
622@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
623@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
624@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
625
626.c.lo:
627@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
628@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
629@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
630@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
631@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
632
633mostlyclean-libtool:
634 -rm -f *.lo
635
636clean-libtool:
637 -rm -rf .libs _libs
638install-libsylph_0includeHEADERS: $(libsylph_0include_HEADERS)
639 @$(NORMAL_INSTALL)
640 @list='$(libsylph_0include_HEADERS)'; test -n "$(libsylph_0includedir)" || list=; \
641 if test -n "$$list"; then \
642 echo " $(MKDIR_P) '$(DESTDIR)$(libsylph_0includedir)'"; \
643 $(MKDIR_P) "$(DESTDIR)$(libsylph_0includedir)" || exit 1; \
644 fi; \
645 for p in $$list; do \
646 if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
647 echo "$$d$$p"; \
648 done | $(am__base_list) | \
649 while read files; do \
650 echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libsylph_0includedir)'"; \
651 $(INSTALL_HEADER) $$files "$(DESTDIR)$(libsylph_0includedir)" || exit $$?; \
652 done
653
654uninstall-libsylph_0includeHEADERS:
655 @$(NORMAL_UNINSTALL)
656 @list='$(libsylph_0include_HEADERS)'; test -n "$(libsylph_0includedir)" || list=; \
657 files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
658 dir='$(DESTDIR)$(libsylph_0includedir)'; $(am__uninstall_files_from_dir)
659
660ID: $(am__tagged_files)
661 $(am__define_uniq_tagged_files); mkid -fID $$unique
662tags: tags-am
663TAGS: tags
664
665tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
666 set x; \
667 here=`pwd`; \
668 $(am__define_uniq_tagged_files); \
669 shift; \
670 if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
671 test -n "$$unique" || unique=$$empty_fix; \
672 if test $$# -gt 0; then \
673 $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
674 "$$@" $$unique; \
675 else \
676 $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
677 $$unique; \
678 fi; \
679 fi
680ctags: ctags-am
681
682CTAGS: ctags
683ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
684 $(am__define_uniq_tagged_files); \
685 test -z "$(CTAGS_ARGS)$$unique" \
686 || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
687 $$unique
688
689GTAGS:
690 here=`$(am__cd) $(top_builddir) && pwd` \
691 && $(am__cd) $(top_srcdir) \
692 && gtags -i $(GTAGS_ARGS) "$$here"
693cscopelist: cscopelist-am
694
695cscopelist-am: $(am__tagged_files)
696 list='$(am__tagged_files)'; \
697 case "$(srcdir)" in \
698 [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
699 *) sdir=$(subdir)/$(srcdir) ;; \
700 esac; \
701 for i in $$list; do \
702 if test -f "$$i"; then \
703 echo "$(subdir)/$$i"; \
704 else \
705 echo "$$sdir/$$i"; \
706 fi; \
707 done >> $(top_builddir)/cscope.files
708
709distclean-tags:
710 -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
711
712distdir: $(DISTFILES)
713 @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
714 topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
715 list='$(DISTFILES)'; \
716 dist_files=`for file in $$list; do echo $$file; done | \
717 sed -e "s|^$$srcdirstrip/||;t" \
718 -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
719 case $$dist_files in \
720 */*) $(MKDIR_P) `echo "$$dist_files" | \
721 sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
722 sort -u` ;; \
723 esac; \
724 for file in $$dist_files; do \
725 if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
726 if test -d $$d/$$file; then \
727 dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
728 if test -d "$(distdir)/$$file"; then \
729 find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
730 fi; \
731 if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
732 cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
733 find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
734 fi; \
735 cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
736 else \
737 test -f "$(distdir)/$$file" \
738 || cp -p $$d/$$file "$(distdir)/$$file" \
739 || exit 1; \
740 fi; \
741 done
742check-am: all-am
743check: $(BUILT_SOURCES)
744 $(MAKE) $(AM_MAKEFLAGS) check-am
745all-am: Makefile $(LTLIBRARIES) $(HEADERS)
746installdirs:
747 for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libsylph_0includedir)"; do \
748 test -z "$$dir" || $(MKDIR_P) "$$dir"; \
749 done
750install: $(BUILT_SOURCES)
751 $(MAKE) $(AM_MAKEFLAGS) install-am
752install-exec: install-exec-am
753install-data: install-data-am
754uninstall: uninstall-am
755
756install-am: all-am
757 @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
758
759installcheck: installcheck-am
760install-strip:
761 if test -z '$(STRIP)'; then \
762 $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
763 install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
764 install; \
765 else \
766 $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
767 install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
768 "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
769 fi
770mostlyclean-generic:
771
772clean-generic:
773
774distclean-generic:
775 -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
776 -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
777
778maintainer-clean-generic:
779 @echo "This command is intended for maintainers to use"
780 @echo "it deletes files that may require special tools to rebuild."
781 -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
782clean: clean-am
783
784clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
785 mostlyclean-am
786
787distclean: distclean-am
788 -rm -rf ./$(DEPDIR)
789 -rm -f Makefile
790distclean-am: clean-am distclean-compile distclean-generic \
791 distclean-tags
792
793dvi: dvi-am
794
795dvi-am:
796
797html: html-am
798
799html-am:
800
801info: info-am
802
803info-am:
804
805install-data-am: install-libsylph_0includeHEADERS
806
807install-dvi: install-dvi-am
808
809install-dvi-am:
810
811install-exec-am: install-libLTLIBRARIES
812
813install-html: install-html-am
814
815install-html-am:
816
817install-info: install-info-am
818
819install-info-am:
820
821install-man:
822
823install-pdf: install-pdf-am
824
825install-pdf-am:
826
827install-ps: install-ps-am
828
829install-ps-am:
830
831installcheck-am:
832
833maintainer-clean: maintainer-clean-am
834 -rm -rf ./$(DEPDIR)
835 -rm -f Makefile
836maintainer-clean-am: distclean-am maintainer-clean-generic
837
838mostlyclean: mostlyclean-am
839
840mostlyclean-am: mostlyclean-compile mostlyclean-generic \
841 mostlyclean-libtool
842
843pdf: pdf-am
844
845pdf-am:
846
847ps: ps-am
848
849ps-am:
850
851uninstall-am: uninstall-libLTLIBRARIES \
852 uninstall-libsylph_0includeHEADERS
853
854.MAKE: all check install install-am install-strip
855
856.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \
857 clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \
858 ctags-am distclean distclean-compile distclean-generic \
859 distclean-libtool distclean-tags distdir dvi dvi-am html \
860 html-am info info-am install install-am install-data \
861 install-data-am install-dvi install-dvi-am install-exec \
862 install-exec-am install-html install-html-am install-info \
863 install-info-am install-libLTLIBRARIES \
864 install-libsylph_0includeHEADERS install-man install-pdf \
865 install-pdf-am install-ps install-ps-am install-strip \
866 installcheck installcheck-am installdirs maintainer-clean \
867 maintainer-clean-generic mostlyclean mostlyclean-compile \
868 mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
869 tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
870 uninstall-libsylph_0includeHEADERS
871
872
873syl-marshal.h: syl-marshal.list
874 $(GLIB_GENMARSHAL) $< --header --prefix=syl_marshal > $@
875
876syl-marshal.c: syl-marshal.list
877 $(GLIB_GENMARSHAL) $< --body --prefix=syl_marshal > $@
878
879# Tell versions [3.59,3.63) of GNU make to not export all variables.
880# Otherwise a system limit (for SysV at least) may be exceeded.
881.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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2009 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <stdio.h>
29#include <errno.h>
30
31#include "folder.h"
32#include "account.h"
33#include "prefs.h"
34#include "prefs_account.h"
35#include "procmsg.h"
36#include "procheader.h"
37#include "utils.h"
38#include "sylmain.h"
39
40PrefsAccount *cur_account;
41
42static GList *account_list = NULL;
43
44GHashTable *address_table;
45
46
47void account_read_config_all(void)
48{
49 GSList *ac_label_list = NULL, *cur;
50 gchar *rcpath;
51 FILE *fp;
52 gchar buf[PREFSBUFSIZE];
53 PrefsAccount *ac_prefs;
54
55 debug_print(_("Reading all config for each account...\n"));
56
57 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACCOUNT_RC, NULL);
58 if ((fp = g_fopen(rcpath, "rb")) == NULL) {
59 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
60 g_free(rcpath);
61 return;
62 }
63 g_free(rcpath);
64
65 while (fgets(buf, sizeof(buf), fp) != NULL) {
66 if (!strncmp(buf, "[Account: ", 10)) {
67 strretchomp(buf);
68 memmove(buf, buf + 1, strlen(buf));
69 buf[strlen(buf) - 1] = '\0';
70 debug_print("Found label: %s\n", buf);
71 ac_label_list = g_slist_append(ac_label_list,
72 g_strdup(buf));
73 }
74 }
75 fclose(fp);
76
77 /* read config data from file */
78 cur_account = NULL;
79 for (cur = ac_label_list; cur != NULL; cur = cur->next) {
80 ac_prefs = prefs_account_new();
81 prefs_account_read_config(ac_prefs, (gchar *)cur->data);
82 account_list = g_list_append(account_list, ac_prefs);
83 if (ac_prefs->is_default)
84 cur_account = ac_prefs;
85 }
86 /* if default is not set, assume first account as default */
87 if (!cur_account && account_list) {
88 ac_prefs = (PrefsAccount *)account_list->data;
89 account_set_as_default(ac_prefs);
90 cur_account = ac_prefs;
91 }
92
93 while (ac_label_list) {
94 g_free(ac_label_list->data);
95 ac_label_list = g_slist_remove(ac_label_list,
96 ac_label_list->data);
97 }
98}
99
100void account_write_config_all(void)
101{
102 prefs_account_write_config_all(account_list);
103}
104
105PrefsAccount *account_get_current_account(void)
106{
107 return cur_account;
108}
109
110PrefsAccount *account_find_from_smtp_server(const gchar *address,
111 const gchar *smtp_server)
112{
113 GList *cur;
114 PrefsAccount *ac;
115
116 g_return_val_if_fail(address != NULL, NULL);
117 g_return_val_if_fail(smtp_server != NULL, NULL);
118
119 for (cur = account_list; cur != NULL; cur = cur->next) {
120 ac = (PrefsAccount *)cur->data;
121 if (!strcmp2(address, ac->address) &&
122 !strcmp2(smtp_server, ac->smtp_server))
123 return ac;
124 }
125
126 return NULL;
127}
128
129/*
130 * account_find_from_address:
131 * @address: Email address string.
132 *
133 * Find a mail (not news) account with the specified email address.
134 *
135 * Return value: The found account, or NULL if not found.
136 */
137PrefsAccount *account_find_from_address(const gchar *address)
138{
139 GList *cur;
140 PrefsAccount *ac;
141
142 g_return_val_if_fail(address != NULL, NULL);
143
144 for (cur = account_list; cur != NULL; cur = cur->next) {
145 ac = (PrefsAccount *)cur->data;
146 if (ac->protocol != A_NNTP && ac->address &&
147 strcasestr(address, ac->address) != NULL)
148 return ac;
149 }
150
151 return NULL;
152}
153
154PrefsAccount *account_find_from_id(gint id)
155{
156 GList *cur;
157 PrefsAccount *ac;
158
159 for (cur = account_list; cur != NULL; cur = cur->next) {
160 ac = (PrefsAccount *)cur->data;
161 if (id == ac->account_id)
162 return ac;
163 }
164
165 return NULL;
166}
167
168PrefsAccount *account_find_from_item(FolderItem *item)
169{
170 PrefsAccount *ac;
171
172 g_return_val_if_fail(item != NULL, NULL);
173
174 ac = account_find_from_item_property(item);
175 if (!ac)
176 ac = item->folder->account;
177
178 return ac;
179}
180
181PrefsAccount *account_find_from_item_property(FolderItem *item)
182{
183 PrefsAccount *ac;
184
185 g_return_val_if_fail(item != NULL, NULL);
186
187 ac = item->account;
188 if (!ac) {
189 FolderItem *cur_item = item->parent;
190 while (cur_item != NULL) {
191 if (cur_item->account && cur_item->ac_apply_sub) {
192 ac = cur_item->account;
193 break;
194 }
195 cur_item = cur_item->parent;
196 }
197 }
198
199 return ac;
200}
201
202PrefsAccount *account_find_from_message_file(const gchar *file)
203{
204 static HeaderEntry hentry[] = {{"From:", NULL, FALSE},
205 {"X-Sylpheed-Account-Id:", NULL, FALSE},
206 {"AID:", NULL, FALSE},
207 {NULL, NULL, FALSE}};
208
209 enum
210 {
211 H_FROM = 0,
212 H_X_SYLPHEED_ACCOUNT_ID = 1,
213 H_AID = 2
214 };
215
216 PrefsAccount *ac = NULL;
217 FILE *fp;
218 gchar *str;
219 gchar buf[BUFFSIZE];
220 gint hnum;
221
222 g_return_val_if_fail(file != NULL, NULL);
223
224 if ((fp = g_fopen(file, "rb")) == NULL) {
225 FILE_OP_ERROR(file, "fopen");
226 return NULL;
227 }
228
229 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
230 != -1) {
231 str = buf + strlen(hentry[hnum].name);
232 if (hnum == H_FROM)
233 ac = account_find_from_address(str);
234 else if (hnum == H_X_SYLPHEED_ACCOUNT_ID || hnum == H_AID) {
235 PrefsAccount *tmp_ac;
236
237 tmp_ac = account_find_from_id(atoi(str));
238 if (tmp_ac) {
239 ac = tmp_ac;
240 break;
241 }
242 }
243 }
244
245 fclose(fp);
246 return ac;
247}
248
249PrefsAccount *account_find_from_msginfo(MsgInfo *msginfo)
250{
251 gchar *file;
252 PrefsAccount *ac;
253
254 file = procmsg_get_message_file(msginfo);
255 ac = account_find_from_message_file(file);
256 g_free(file);
257
258 if (!ac && msginfo->folder)
259 ac = account_find_from_item(msginfo->folder);
260
261 return ac;
262}
263
264gboolean account_address_exist(const gchar *address)
265{
266 if (!address_table) {
267 GList *cur;
268
269 address_table = g_hash_table_new(g_str_hash, g_str_equal);
270 for (cur = account_list; cur != NULL; cur = cur->next) {
271 PrefsAccount *ac = (PrefsAccount *)cur->data;
272
273 if (ac->address)
274 g_hash_table_insert(address_table, ac->address,
275 GINT_TO_POINTER(1));
276 }
277 }
278
279 return (gboolean)g_hash_table_lookup(address_table, address);
280}
281
282void account_foreach(AccountFunc func, gpointer user_data)
283{
284 GList *cur;
285
286 for (cur = account_list; cur != NULL; cur = cur->next)
287 if (func((PrefsAccount *)cur->data, user_data) != 0)
288 return;
289}
290
291GList *account_get_list(void)
292{
293 return account_list;
294}
295
296void account_list_free(void)
297{
298 g_list_free(account_list);
299 account_list = NULL;
300}
301
302void account_append(PrefsAccount *ac_prefs)
303{
304 account_list = g_list_append(account_list, ac_prefs);
305 account_updated();
306}
307
308void account_set_as_default(PrefsAccount *ac_prefs)
309{
310 PrefsAccount *ap;
311 GList *cur;
312
313 for (cur = account_list; cur != NULL; cur = cur->next) {
314 ap = (PrefsAccount *)cur->data;
315 if (ap->is_default)
316 ap->is_default = FALSE;
317 }
318
319 ac_prefs->is_default = TRUE;
320}
321
322PrefsAccount *account_get_default(void)
323{
324 PrefsAccount *ap;
325 GList *cur;
326
327 for (cur = account_list; cur != NULL; cur = cur->next) {
328 ap = (PrefsAccount *)cur->data;
329 if (ap->is_default)
330 return ap;
331 }
332
333 return NULL;
334}
335
336#if 0
337void account_set_missing_folder(void)
338{
339 PrefsAccount *ap;
340 GList *cur;
341
342 for (cur = account_list; cur != NULL; cur = cur->next) {
343 ap = (PrefsAccount *)cur->data;
344 if ((ap->protocol == A_IMAP4 || ap->protocol == A_NNTP) &&
345 !ap->folder) {
346 Folder *folder;
347
348 if (ap->protocol == A_IMAP4) {
349 folder = folder_new(F_IMAP, ap->account_name,
350 ap->recv_server);
351 } else {
352 folder = folder_new(F_NEWS, ap->account_name,
353 ap->nntp_server);
354 }
355
356 folder->account = ap;
357 ap->folder = REMOTE_FOLDER(folder);
358 folder_add(folder);
359 if (ap->protocol == A_IMAP4) {
360 if (main_window_toggle_online_if_offline
361 (main_window_get())) {
362 folder->klass->create_tree(folder);
363 statusbar_pop_all();
364 }
365 }
366 }
367 }
368}
369#endif
370
371FolderItem *account_get_special_folder(PrefsAccount *ac_prefs,
372 SpecialFolderItemType type)
373{
374 FolderItem *item = NULL;
375
376 g_return_val_if_fail(ac_prefs != NULL, NULL);
377
378 switch (type) {
379 case F_INBOX:
380 if (ac_prefs->folder)
381 item = FOLDER(ac_prefs->folder)->inbox;
382 if (!item)
383 item = folder_get_default_inbox();
384 break;
385 case F_OUTBOX:
386 if (ac_prefs->set_sent_folder && ac_prefs->sent_folder) {
387 item = folder_find_item_from_identifier
388 (ac_prefs->sent_folder);
389 }
390 if (!item) {
391 if (ac_prefs->folder)
392 item = FOLDER(ac_prefs->folder)->outbox;
393 if (!item)
394 item = folder_get_default_outbox();
395 }
396 break;
397 case F_DRAFT:
398 if (ac_prefs->set_draft_folder && ac_prefs->draft_folder) {
399 item = folder_find_item_from_identifier
400 (ac_prefs->draft_folder);
401 }
402 if (!item) {
403 if (ac_prefs->folder)
404 item = FOLDER(ac_prefs->folder)->draft;
405 if (!item)
406 item = folder_get_default_draft();
407 }
408 break;
409 case F_QUEUE:
410 if (ac_prefs->set_queue_folder && ac_prefs->queue_folder) {
411 item = folder_find_item_from_identifier
412 (ac_prefs->queue_folder);
413 /* only allow queue-type folder */
414 if (item && item->stype != F_QUEUE)
415 item = NULL;
416 }
417 if (!item) {
418 if (ac_prefs->folder)
419 item = FOLDER(ac_prefs->folder)->queue;
420 if (!item)
421 item = folder_get_default_queue();
422 }
423 break;
424 case F_TRASH:
425 if (ac_prefs->set_trash_folder && ac_prefs->trash_folder) {
426 item = folder_find_item_from_identifier
427 (ac_prefs->trash_folder);
428 }
429 if (!item) {
430 if (ac_prefs->folder)
431 item = FOLDER(ac_prefs->folder)->trash;
432 if (!item)
433 item = folder_get_default_trash();
434 }
435 break;
436 default:
437 break;
438 }
439
440 return item;
441}
442
443void account_destroy(PrefsAccount *ac_prefs)
444{
445 g_return_if_fail(ac_prefs != NULL);
446
447 folder_unref_account_all(ac_prefs);
448
449 account_list = g_list_remove(account_list, ac_prefs);
450 if (cur_account == ac_prefs)
451 cur_account = NULL;
452 prefs_account_free(ac_prefs);
453
454 if (!cur_account && account_list) {
455 cur_account = account_get_default();
456 if (!cur_account) {
457 ac_prefs = (PrefsAccount *)account_list->data;
458 account_set_as_default(ac_prefs);
459 cur_account = ac_prefs;
460 }
461 }
462
463 account_updated();
464}
465
466static guint account_update_lock_count = 0;
467
468void account_update_lock(void)
469{
470 account_update_lock_count++;
471}
472
473void account_update_unlock(void)
474{
475 if (account_update_lock_count > 0)
476 account_update_lock_count--;
477}
478
479void account_updated(void)
480{
481 if (account_update_lock_count)
482 return;
483
484 if (address_table) {
485 g_hash_table_destroy(address_table);
486 address_table = NULL;
487 }
488
489 if (syl_app_get())
490 g_signal_emit_by_name(syl_app_get(), "account-updated");
491}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2009 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __ACCOUNT_H__
21#define __ACCOUNT_H__
22
23#include <glib.h>
24
25#include "prefs.h"
26#include "prefs_account.h"
27#include "folder.h"
28#include "procmsg.h"
29
30typedef gint (*AccountFunc) (PrefsAccount *ac_prefs,
31 gpointer user_data);
32
33extern PrefsAccount *cur_account;
34
35void account_read_config_all (void);
36void account_write_config_all (void);
37
38PrefsAccount *account_get_current_account (void);
39
40PrefsAccount *account_find_from_smtp_server (const gchar *address,
41 const gchar *smtp_server);
42PrefsAccount *account_find_from_address (const gchar *address);
43PrefsAccount *account_find_from_id (gint id);
44PrefsAccount *account_find_from_item (FolderItem *item);
45PrefsAccount *account_find_from_item_property (FolderItem *item);
46PrefsAccount *account_find_from_message_file (const gchar *file);
47PrefsAccount *account_find_from_msginfo (MsgInfo *msginfo);
48
49gboolean account_address_exist (const gchar *address);
50
51void account_foreach (AccountFunc func,
52 gpointer user_data);
53GList *account_get_list (void);
54void account_list_free (void);
55void account_append (PrefsAccount *ac_prefs);
56
57void account_set_as_default (PrefsAccount *ac_prefs);
58PrefsAccount *account_get_default (void);
59
60//void account_set_missing_folder(void);
61FolderItem *account_get_special_folder(PrefsAccount *ac_prefs,
62 SpecialFolderItemType type);
63
64void account_destroy (PrefsAccount *ac_prefs);
65
66void account_update_lock (void);
67void account_update_unlock (void);
68void account_updated (void);
69
70#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include <glib.h>
21#include <ctype.h>
22#include <string.h>
23
24#include "base64.h"
25
26static const gchar base64char[64] =
27 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
28
29static const gchar base64val[128] = {
30 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
31 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
32 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
33 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
34 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
35 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
36 -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
37 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
38};
39
40#define BASE64VAL(c) (isascii((guchar)c) ? base64val[(gint)(c)] : -1)
41
42void base64_encode(gchar *out, const guchar *in, gint inlen)
43{
44 const guchar *inp = in;
45 gchar *outp = out;
46
47 while (inlen >= 3) {
48 *outp++ = base64char[(inp[0] >> 2) & 0x3f];
49 *outp++ = base64char[((inp[0] & 0x03) << 4) |
50 ((inp[1] >> 4) & 0x0f)];
51 *outp++ = base64char[((inp[1] & 0x0f) << 2) |
52 ((inp[2] >> 6) & 0x03)];
53 *outp++ = base64char[inp[2] & 0x3f];
54
55 inp += 3;
56 inlen -= 3;
57 }
58
59 if (inlen > 0) {
60 *outp++ = base64char[(inp[0] >> 2) & 0x3f];
61 if (inlen == 1) {
62 *outp++ = base64char[(inp[0] & 0x03) << 4];
63 *outp++ = '=';
64 } else {
65 *outp++ = base64char[((inp[0] & 0x03) << 4) |
66 ((inp[1] >> 4) & 0x0f)];
67 *outp++ = base64char[((inp[1] & 0x0f) << 2)];
68 }
69 *outp++ = '=';
70 }
71
72 *outp = '\0';
73}
74
75gint base64_decode(guchar *out, const gchar *in, gint inlen)
76{
77 const gchar *inp = in;
78 guchar *outp = out;
79 gchar buf[4];
80
81 if (inlen < 0)
82 inlen = G_MAXINT;
83
84 while (inlen >= 4 && *inp != '\0') {
85 buf[0] = *inp++;
86 inlen--;
87 if (BASE64VAL(buf[0]) == -1) break;
88
89 buf[1] = *inp++;
90 inlen--;
91 if (BASE64VAL(buf[1]) == -1) break;
92
93 buf[2] = *inp++;
94 inlen--;
95 if (buf[2] != '=' && BASE64VAL(buf[2]) == -1) break;
96
97 buf[3] = *inp++;
98 inlen--;
99 if (buf[3] != '=' && BASE64VAL(buf[3]) == -1) break;
100
101 *outp++ = ((BASE64VAL(buf[0]) << 2) & 0xfc) |
102 ((BASE64VAL(buf[1]) >> 4) & 0x03);
103 if (buf[2] != '=') {
104 *outp++ = ((BASE64VAL(buf[1]) & 0x0f) << 4) |
105 ((BASE64VAL(buf[2]) >> 2) & 0x0f);
106 if (buf[3] != '=') {
107 *outp++ = ((BASE64VAL(buf[2]) & 0x03) << 6) |
108 (BASE64VAL(buf[3]) & 0x3f);
109 }
110 }
111 }
112
113 return outp - out;
114}
115
116Base64Decoder *base64_decoder_new(void)
117{
118 Base64Decoder *decoder;
119
120 decoder = g_new0(Base64Decoder, 1);
121 return decoder;
122}
123
124void base64_decoder_free(Base64Decoder *decoder)
125{
126 g_free(decoder);
127}
128
129gint base64_decoder_decode(Base64Decoder *decoder,
130 const gchar *in, guchar *out)
131{
132 gint len, total_len = 0;
133 gint buf_len;
134 gchar buf[4];
135
136 g_return_val_if_fail(decoder != NULL, -1);
137 g_return_val_if_fail(in != NULL, -1);
138 g_return_val_if_fail(out != NULL, -1);
139
140 buf_len = decoder->buf_len;
141 memcpy(buf, decoder->buf, sizeof(buf));
142
143 for (;;) {
144 while (buf_len < 4) {
145 gchar c = *in;
146
147 in++;
148 if (c == '\0') break;
149 if (c == '\r' || c == '\n') continue;
150 if (c != '=' && BASE64VAL(c) == -1)
151 return -1;
152 buf[buf_len++] = c;
153 }
154 if (buf_len < 4 || buf[0] == '=' || buf[1] == '=') {
155 decoder->buf_len = buf_len;
156 memcpy(decoder->buf, buf, sizeof(buf));
157 return total_len;
158 }
159 len = base64_decode(out, buf, 4);
160 out += len;
161 total_len += len;
162 buf_len = 0;
163 if (len < 3) {
164 decoder->buf_len = 0;
165 return total_len;
166 }
167 }
168}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __BASE64_H__
21#define __BASE64_H__
22
23#include <glib.h>
24
25typedef struct _Base64Decoder Base64Decoder;
26
27struct _Base64Decoder
28{
29 gint buf_len;
30 gchar buf[4];
31};
32
33void base64_encode (gchar *out,
34 const guchar *in,
35 gint inlen);
36gint base64_decode (guchar *out,
37 const gchar *in,
38 gint inlen);
39
40Base64Decoder *base64_decoder_new (void);
41void base64_decoder_free (Base64Decoder *decoder);
42gint base64_decoder_decode (Base64Decoder *decoder,
43 const gchar *in,
44 guchar *out);
45
46#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2017 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <string.h>
29#include <ctype.h>
30#include <stdlib.h>
31#include <errno.h>
32
33#if HAVE_LOCALE_H
34# include <locale.h>
35#endif
36
37#include <iconv.h>
38
39#include "codeconv.h"
40#include "prefs_common.h"
41#include "unmime.h"
42#include "base64.h"
43#include "quoted-printable.h"
44#include "utils.h"
45
46typedef enum
47{
48 JIS_ASCII,
49 JIS_KANJI,
50 JIS_HWKANA,
51 JIS_AUXKANJI,
52 JIS_UDC
53} JISState;
54
55#define SUBST_CHAR '_'
56#define ESC '\033'
57#define SO 0x0e
58#define SI 0x0f
59#define SS2 0x8e
60#define SS3 0x8f
61
62#define iseuckanji(c) \
63 (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xfe)
64#define iseuchwkana1(c) \
65 (((c) & 0xff) == SS2)
66#define iseuchwkana2(c) \
67 (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xdf)
68#define iseucaux(c) \
69 (((c) & 0xff) == SS3)
70
71#define issjiskanji1(c) \
72 ((((c) & 0xff) >= 0x81 && ((c) & 0xff) <= 0x9f) || \
73 (((c) & 0xff) >= 0xe0 && ((c) & 0xff) <= 0xef))
74#define issjiskanji2(c) \
75 ((((c) & 0xff) >= 0x40 && ((c) & 0xff) <= 0x7e) || \
76 (((c) & 0xff) >= 0x80 && ((c) & 0xff) <= 0xfc))
77#define issjishwkana(c) \
78 (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xdf)
79#define issjisext(c) \
80 (((c) & 0xff) >= 0xf0 && ((c) & 0xff) <= 0xfc)
81#define issjisudc(c) \
82 (((c) & 0xff) >= 0xf0 && ((c) & 0xff) <= 0xf9)
83#define issjisibmext(c1, c2) \
84 ((((c1) & 0xff) >= 0xfa && ((c1) & 0xff) <= 0xfb && \
85 issjiskanji2(c2)) || \
86 (((c1) & 0xff) == 0xfc && \
87 ((c2) & 0xff) >= 0x40 && ((c2) & 0xff) <= 0x4b))
88
89#define isjiskanji(c) \
90 (((c) & 0xff) >= 0x21 && ((c) & 0xff) <= 0x7e)
91#define isjishwkana(c) \
92 (((c) & 0xff) >= 0x21 && ((c) & 0xff) <= 0x5f)
93#define isjisudc(c) \
94 (((c) & 0xff) >= 0x21 && ((c) & 0xff) <= 0x34)
95#define isjisudclow(c) \
96 (((c) & 0xff) >= 0x21 && ((c) & 0xff) <= 0x2a)
97#define isjisudchigh(c) \
98 (((c) & 0xff) >= 0x2b && ((c) & 0xff) <= 0x34)
99
100/* U+0080 - U+07FF */
101#define isutf8_2_1(c) \
102 (((c) & 0xe0) == 0xc0)
103#define isutf8_2_2(c) \
104 (((c) & 0xc0) == 0x80)
105/* U+0800 - U+FFFF */
106#define isutf8_3_1(c) \
107 (((c) & 0xf0) == 0xe0)
108#define isutf8_3_2(c) \
109 (((c) & 0xc0) == 0x80)
110
111#define isutf8bom(s) \
112 (((*(s)) & 0xff) == 0xef && ((*(s + 1)) & 0xff) == 0xbb && \
113 ((*(s + 2)) & 0xff) == 0xbf)
114
115#define K_IN() \
116 if (state != JIS_KANJI) { \
117 *out++ = ESC; \
118 *out++ = '$'; \
119 *out++ = 'B'; \
120 state = JIS_KANJI; \
121 }
122
123#define K_OUT() \
124 if (state != JIS_ASCII) { \
125 *out++ = ESC; \
126 *out++ = '('; \
127 *out++ = 'B'; \
128 state = JIS_ASCII; \
129 }
130
131#define HW_IN() \
132 if (state != JIS_HWKANA) { \
133 *out++ = ESC; \
134 *out++ = '('; \
135 *out++ = 'I'; \
136 state = JIS_HWKANA; \
137 }
138
139#define AUX_IN() \
140 if (state != JIS_AUXKANJI) { \
141 *out++ = ESC; \
142 *out++ = '$'; \
143 *out++ = '('; \
144 *out++ = 'D'; \
145 state = JIS_AUXKANJI; \
146 }
147
148#define UDC_IN() \
149 if (state != JIS_UDC) { \
150 *out++ = ESC; \
151 *out++ = '$'; \
152 *out++ = '('; \
153 *out++ = '?'; \
154 state = JIS_UDC; \
155 }
156
157static ConvADType conv_ad_type = C_AD_BY_LOCALE;
158
159static gchar *conv_jistoeuc(const gchar *inbuf, gint *error);
160static gchar *conv_jistosjis(const gchar *inbuf, gint *error);
161static gchar *conv_euctojis(const gchar *inbuf, gint *error);
162static gchar *conv_sjistojis(const gchar *inbuf, gint *error);
163static gchar *conv_sjistoeuc(const gchar *inbuf, gint *error);
164
165static gchar *conv_jistoutf8(const gchar *inbuf, gint *error);
166static gchar *conv_sjistoutf8(const gchar *inbuf, gint *error);
167static gchar *conv_euctoutf8(const gchar *inbuf, gint *error);
168static gchar *conv_anytoutf8(const gchar *inbuf, gint *error);
169
170static gchar *conv_utf8toeuc(const gchar *inbuf, gint *error);
171static gchar *conv_utf8tojis(const gchar *inbuf, gint *error);
172static gchar *conv_utf8tosjis(const gchar *inbuf, gint *error);
173
174/* static void conv_unreadable_eucjp(gchar *str); */
175static void conv_unreadable_8bit(gchar *str);
176/* static void conv_unreadable_latin(gchar *str); */
177
178static gchar *conv_jistodisp(const gchar *inbuf, gint *error);
179static gchar *conv_sjistodisp(const gchar *inbuf, gint *error);
180static gchar *conv_euctodisp(const gchar *inbuf, gint *error);
181
182static gchar *conv_anytodisp(const gchar *inbuf, gint *error);
183static gchar *conv_ustodisp(const gchar *inbuf, gint *error);
184static gchar *conv_noconv(const gchar *inbuf, gint *error);
185
186static gchar *conv_jistoeuc(const gchar *inbuf, gint *error)
187{
188 gchar *outbuf;
189 const guchar *in = (guchar *)inbuf;
190 guchar *out;
191 JISState state = JIS_ASCII;
192 gint error_ = 0;
193
194 outbuf = g_malloc(strlen(inbuf) * 2 + 1);
195 out = (guchar *)outbuf;
196
197 while (*in != '\0') {
198 if (*in == ESC) {
199 in++;
200 if (*in == '$') {
201 if (*(in + 1) == '@' || *(in + 1) == 'B') {
202 state = JIS_KANJI;
203 in += 2;
204 } else if (*(in + 1) == '(' &&
205 *(in + 2) == 'D') {
206 state = JIS_AUXKANJI;
207 in += 3;
208 } else {
209 /* unknown escape sequence */
210 error_ = -1;
211 state = JIS_ASCII;
212 }
213 } else if (*in == '(') {
214 if (*(in + 1) == 'B' || *(in + 1) == 'J') {
215 state = JIS_ASCII;
216 in += 2;
217 } else if (*(in + 1) == 'I') {
218 state = JIS_HWKANA;
219 in += 2;
220 } else {
221 /* unknown escape sequence */
222 error_ = -1;
223 state = JIS_ASCII;
224 }
225 } else {
226 /* unknown escape sequence */
227 error_ = -1;
228 state = JIS_ASCII;
229 }
230 } else if (*in == 0x0e) {
231 state = JIS_HWKANA;
232 in++;
233 } else if (*in == 0x0f) {
234 state = JIS_ASCII;
235 in++;
236 } else {
237 switch (state) {
238 case JIS_ASCII:
239 *out++ = *in++;
240 break;
241 case JIS_KANJI:
242 *out++ = *in++ | 0x80;
243 if (*in == '\0') break;
244 *out++ = *in++ | 0x80;
245 break;
246 case JIS_HWKANA:
247 *out++ = 0x8e;
248 *out++ = *in++ | 0x80;
249 break;
250 case JIS_AUXKANJI:
251 *out++ = 0x8f;
252 *out++ = *in++ | 0x80;
253 if (*in == '\0') break;
254 *out++ = *in++ | 0x80;
255 break;
256 default:
257 *out++ = *in++;
258 break;
259 }
260 }
261 }
262
263 *out = '\0';
264
265 if (error)
266 *error = error_;
267
268 return outbuf;
269}
270
271static gchar *conv_jistosjis(const gchar *inbuf, gint *error)
272{
273 gchar *outbuf;
274 const guchar *in = (guchar *)inbuf;
275 guchar *out;
276 JISState state = JIS_ASCII;
277 gint error_ = 0;
278
279 outbuf = g_malloc(strlen(inbuf) * 2 + 1);
280 out = (guchar *)outbuf;
281
282 while (*in != '\0') {
283 if (*in == ESC) {
284 in++;
285 if (*in == '$') {
286 if (*(in + 1) == '@' || *(in + 1) == 'B') {
287 state = JIS_KANJI;
288 in += 2;
289 } else if (*(in + 1) == '(' &&
290 *(in + 2) == '?') {
291 /* ISO-2022-JP-MS extention */
292 state = JIS_UDC;
293 in += 3;
294 } else {
295 /* unknown escape sequence */
296 error_ = -1;
297 state = JIS_ASCII;
298 }
299 } else if (*in == '(') {
300 if (*(in + 1) == 'B' || *(in + 1) == 'J') {
301 state = JIS_ASCII;
302 in += 2;
303 } else if (*(in + 1) == 'I') {
304 state = JIS_HWKANA;
305 in += 2;
306 } else {
307 /* unknown escape sequence */
308 error_ = -1;
309 state = JIS_ASCII;
310 }
311 } else {
312 /* unknown escape sequence */
313 error_ = -1;
314 state = JIS_ASCII;
315 }
316 } else if (*in == SO) {
317 state = JIS_HWKANA;
318 in++;
319 } else if (*in == SI) {
320 state = JIS_ASCII;
321 in++;
322 } else {
323 switch (state) {
324 case JIS_ASCII:
325 *out++ = *in++;
326 break;
327 case JIS_HWKANA:
328 *out++ = *in++ | 0x80;
329 break;
330 case JIS_KANJI:
331 if ((isjiskanji(*in) ||
332 (*in >= 0x7f && *in <= 0x97)) &&
333 isjiskanji(*(in + 1))) {
334 *out++ = ((*in < 0x5f)
335 ? (((*in - 0x21) / 2) + 0x81)
336 : (((*in - 0x21) / 2) + 0xc1));
337 *out++ = ((*in % 2)
338 ? ((*(in + 1) + ((*(in + 1) < 0x60)
339 ? 0x1f : 0x20)))
340 : *(in + 1) + 0x7e);
341 in += 2;
342 } else {
343 error_ = -1;
344 *out++ = SUBST_CHAR;
345 in++;
346 if (*in != '\0') {
347 *out++ = SUBST_CHAR;
348 in++;
349 }
350 }
351 break;
352 case JIS_UDC:
353 if (isjisudc(*in) && isjiskanji(*(in + 1))) {
354 *out++ = (((*in - 0x21) / 2) + 0xf0);
355 *out++ = ((*in % 2)
356 ? ((*(in + 1) + ((*(in + 1) < 0x60)
357 ? 0x1f : 0x20)))
358 : *(in + 1) + 0x7e);
359 in += 2;
360 } else {
361 error_ = -1;
362 *out++ = SUBST_CHAR;
363 in++;
364 if (*in != '\0') {
365 *out++ = SUBST_CHAR;
366 in++;
367 }
368 }
369 break;
370 default:
371 *out++ = *in++;
372 break;
373 }
374 }
375 }
376
377 *out = '\0';
378
379 if (error)
380 *error = error_;
381
382 return outbuf;
383}
384
385#define JIS_HWDAKUTEN 0x5e
386#define JIS_HWHANDAKUTEN 0x5f
387
388static gint conv_jis_hantozen(guchar *outbuf, guchar jis_code, guchar sound_sym)
389{
390 static guint16 h2z_tbl[] = {
391 /* 0x20 - 0x2f */
392 0x0000, 0x2123, 0x2156, 0x2157, 0x2122, 0x2126, 0x2572, 0x2521,
393 0x2523, 0x2525, 0x2527, 0x2529, 0x2563, 0x2565, 0x2567, 0x2543,
394 /* 0x30 - 0x3f */
395 0x213c, 0x2522, 0x2524, 0x2526, 0x2528, 0x252a, 0x252b, 0x252d,
396 0x252f, 0x2531, 0x2533, 0x2535, 0x2537, 0x2539, 0x253b, 0x253d,
397 /* 0x40 - 0x4f */
398 0x253f, 0x2541, 0x2544, 0x2546, 0x2548, 0x254a, 0x254b, 0x254c,
399 0x254d, 0x254e, 0x254f, 0x2552, 0x2555, 0x2558, 0x255b, 0x255e,
400 /* 0x50 - 0x5f */
401 0x255f, 0x2560, 0x2561, 0x2562, 0x2564, 0x2566, 0x2568, 0x2569,
402 0x256a, 0x256b, 0x256c, 0x256d, 0x256f, 0x2573, 0x212b, 0x212c
403 };
404
405 static guint16 dakuten_tbl[] = {
406 /* 0x30 - 0x3f */
407 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x252c, 0x252e,
408 0x2530, 0x2532, 0x2534, 0x2536, 0x2538, 0x253a, 0x253c, 0x253e,
409 /* 0x40 - 0x4f */
410 0x2540, 0x2542, 0x2545, 0x2547, 0x2549, 0x0000, 0x0000, 0x0000,
411 0x0000, 0x0000, 0x2550, 0x2553, 0x2556, 0x2559, 0x255c, 0x0000
412 };
413
414 static guint16 handakuten_tbl[] = {
415 /* 0x4a - 0x4e */
416 0x2551, 0x2554, 0x2557, 0x255a, 0x255d
417 };
418
419 guint16 out_code;
420
421 jis_code &= 0x7f;
422 sound_sym &= 0x7f;
423
424 if (jis_code < 0x21 || jis_code > 0x5f)
425 return 0;
426
427 if (sound_sym == JIS_HWDAKUTEN &&
428 jis_code >= 0x36 && jis_code <= 0x4e) {
429 out_code = dakuten_tbl[jis_code - 0x30];
430 if (out_code != 0) {
431 *outbuf = out_code >> 8;
432 *(outbuf + 1) = out_code & 0xff;
433 return 2;
434 }
435 }
436
437 if (sound_sym == JIS_HWHANDAKUTEN &&
438 jis_code >= 0x4a && jis_code <= 0x4e) {
439 out_code = handakuten_tbl[jis_code - 0x4a];
440 *outbuf = out_code >> 8;
441 *(outbuf + 1) = out_code & 0xff;
442 return 2;
443 }
444
445 out_code = h2z_tbl[jis_code - 0x20];
446 *outbuf = out_code >> 8;
447 *(outbuf + 1) = out_code & 0xff;
448 return 1;
449}
450
451static gchar *conv_euctojis(const gchar *inbuf, gint *error)
452{
453 gchar *outbuf;
454 const guchar *in = (guchar *)inbuf;
455 guchar *out;
456 JISState state = JIS_ASCII;
457 gint error_ = 0;
458
459 outbuf = g_malloc(strlen(inbuf) * 3 + 4);
460 out = (guchar *)outbuf;
461
462 while (*in != '\0') {
463 if (isascii(*in)) {
464 K_OUT();
465 *out++ = *in++;
466 } else if (iseuckanji(*in)) {
467 if (iseuckanji(*(in + 1))) {
468 K_IN();
469 *out++ = *in++ & 0x7f;
470 *out++ = *in++ & 0x7f;
471 } else {
472 error_ = -1;
473 K_OUT();
474 *out++ = SUBST_CHAR;
475 in++;
476 if (*in != '\0' && !isascii(*in)) {
477 *out++ = SUBST_CHAR;
478 in++;
479 }
480 }
481 } else if (iseuchwkana1(*in)) {
482 if (iseuchwkana2(*(in + 1))) {
483 if (prefs_common.allow_jisx0201_kana) {
484 HW_IN();
485 in++;
486 *out++ = *in++ & 0x7f;
487 } else {
488 guchar jis_ch[2];
489 gint len;
490
491 if (iseuchwkana1(*(in + 2)) &&
492 iseuchwkana2(*(in + 3)))
493 len = conv_jis_hantozen
494 (jis_ch,
495 *(in + 1), *(in + 3));
496 else
497 len = conv_jis_hantozen
498 (jis_ch,
499 *(in + 1), '\0');
500 if (len == 0)
501 in += 2;
502 else {
503 K_IN();
504 in += len * 2;
505 *out++ = jis_ch[0];
506 *out++ = jis_ch[1];
507 }
508 }
509 } else {
510 error_ = -1;
511 K_OUT();
512 in++;
513 if (*in != '\0' && !isascii(*in)) {
514 *out++ = SUBST_CHAR;
515 in++;
516 }
517 }
518 } else if (iseucaux(*in)) {
519 in++;
520 if (iseuckanji(*in) && iseuckanji(*(in + 1))) {
521 AUX_IN();
522 *out++ = *in++ & 0x7f;
523 *out++ = *in++ & 0x7f;
524 } else {
525 error_ = -1;
526 K_OUT();
527 if (*in != '\0' && !isascii(*in)) {
528 *out++ = SUBST_CHAR;
529 in++;
530 if (*in != '\0' && !isascii(*in)) {
531 *out++ = SUBST_CHAR;
532 in++;
533 }
534 }
535 }
536 } else {
537 error_ = -1;
538 K_OUT();
539 *out++ = SUBST_CHAR;
540 in++;
541 }
542 }
543
544 K_OUT();
545 *out = '\0';
546
547 if (error)
548 *error = error_;
549
550 return outbuf;
551}
552
553#define sjistoidx(c1, c2) \
554 (((c1) > 0x9f) \
555 ? (((c1) - 0xc1) * 188 + (c2) - (((c2) > 0x7e) ? 0x41 : 0x40)) \
556 : (((c1) - 0x81) * 188 + (c2) - (((c2) > 0x7e) ? 0x41 : 0x40)))
557#define idxtojis1(c) (((c) / 94) + 0x21)
558#define idxtojis2(c) (((c) % 94) + 0x21)
559
560static guint conv_idx_ibmtonec(guint idx)
561{
562 if (idx >= sjistoidx(0xfa, 0x5c))
563 idx -= sjistoidx(0xfa, 0x5c)
564 - sjistoidx(0xed, 0x40);
565/* else if (idx == sjistoidx(0xfa, 0x5b)) */
566/* idx = sjistoidx(0x81, 0xe6); */
567/* else if (idx == sjistoidx(0xfa, 0x5a)) */
568/* idx = sjistoidx(0x87, 0x84); */
569/* else if (idx == sjistoidx(0xfa, 0x59)) */
570/* idx = sjistoidx(0x87, 0x82); */
571/* else if (idx == sjistoidx(0xfa, 0x58)) */
572/* idx = sjistoidx(0x87, 0x8a); */
573 else if (idx >= sjistoidx(0xfa, 0x55))
574 idx -= sjistoidx(0xfa, 0x55)
575 - sjistoidx(0xee, 0xfa);
576/* else if (idx == sjistoidx(0xfa, 0x54)) */
577/* idx = sjistoidx(0x81, 0xca); */
578/* else if (idx >= sjistoidx(0xfa, 0x4a)) */
579/* idx -= sjistoidx(0xfa, 0x4a) */
580/* - sjistoidx(0x87, 0x54); */
581 else if (idx >= sjistoidx(0xfa, 0x40))
582 idx -= sjistoidx(0xfa, 0x40)
583 - sjistoidx(0xee, 0xef);
584 return idx;
585}
586
587static gchar *conv_sjistojis(const gchar *inbuf, gint *error)
588{
589 gchar *outbuf;
590 const guchar *in = (guchar *)inbuf;
591 guchar *out;
592 JISState state = JIS_ASCII;
593 gint error_ = 0;
594 guint idx;
595
596 outbuf = g_malloc(strlen(inbuf) * 5 + 4);
597 out = (guchar *)outbuf;
598
599 while (*in != '\0') {
600 if (isascii(*in)) {
601 K_OUT();
602 *out++ = *in++;
603 } else if (issjiskanji1(*in)) {
604 if (issjiskanji2(*(in + 1))) {
605 K_IN();
606 idx = sjistoidx(*in, *(in + 1));
607 *out++ = idxtojis1(idx);
608 *out++ = idxtojis2(idx);
609 in += 2;
610 } else {
611 error_ = -1;
612 K_OUT();
613 *out++ = SUBST_CHAR;
614 in++;
615 if (*in != '\0' && !isascii(*in)) {
616 *out++ = SUBST_CHAR;
617 in++;
618 }
619 }
620 } else if (issjishwkana(*in)) {
621 if (prefs_common.allow_jisx0201_kana) {
622 HW_IN();
623 *out++ = *in++ & 0x7f;
624 } else {
625 guchar jis_ch[2];
626 gint len;
627
628 if (issjishwkana(*(in + 1)))
629 len = conv_jis_hantozen
630 (jis_ch,
631 *in, *(in + 1));
632 else
633 len = conv_jis_hantozen
634 (jis_ch,
635 *in, '\0');
636 if (len == 0)
637 in++;
638 else {
639 K_IN();
640 in += len;
641 *out++ = jis_ch[0];
642 *out++ = jis_ch[1];
643 }
644 }
645 } else if (issjisibmext(*in, *(in + 1))) {
646 K_IN();
647 idx = sjistoidx(*in, *(in + 1));
648 idx = conv_idx_ibmtonec(idx);
649 *out++ = idxtojis1(idx);
650 *out++ = idxtojis2(idx);
651 in += 2;
652#if 0
653 } else if (issjisudc(*in)) {
654 UDC_IN();
655 idx = sjistoidx(*in, *(in + 1))
656 - sjistoidx(0xf0, 0x40);
657 *out++ = idxtojis1(idx);
658 *out++ = idxtojis2(idx);
659 in += 2;
660#endif
661 } else if (issjisext(*in)) {
662 error_ = -1;
663 K_OUT();
664 *out++ = SUBST_CHAR;
665 in++;
666 if (*in != '\0' && !isascii(*in)) {
667 *out++ = SUBST_CHAR;
668 in++;
669 }
670 } else {
671 error_ = -1;
672 K_OUT();
673 *out++ = SUBST_CHAR;
674 in++;
675 }
676 }
677
678 K_OUT();
679 *out = '\0';
680
681 if (error)
682 *error = error_;
683
684 return outbuf;
685}
686
687static gchar *conv_sjistoeuc(const gchar *inbuf, gint *error)
688{
689 gchar *outbuf;
690 const guchar *in = (guchar *)inbuf;
691 guchar *out;
692 gint error_ = 0;
693
694 outbuf = g_malloc(strlen(inbuf) * 2 + 1);
695 out = (guchar *)outbuf;
696
697 while (*in != '\0') {
698 if (isascii(*in)) {
699 *out++ = *in++;
700 } else if (issjiskanji1(*in)) {
701 if (issjiskanji2(*(in + 1))) {
702 guchar out1 = *in;
703 guchar out2 = *(in + 1);
704 guchar row;
705
706 row = out1 < 0xa0 ? 0x70 : 0xb0;
707 if (out2 < 0x9f) {
708 out1 = (out1 - row) * 2 - 1;
709 out2 -= out2 > 0x7f ? 0x20 : 0x1f;
710 } else {
711 out1 = (out1 - row) * 2;
712 out2 -= 0x7e;
713 }
714
715 *out++ = out1 | 0x80;
716 *out++ = out2 | 0x80;
717 in += 2;
718 } else {
719 error_ = -1;
720 *out++ = SUBST_CHAR;
721 in++;
722 if (*in != '\0' && !isascii(*in)) {
723 *out++ = SUBST_CHAR;
724 in++;
725 }
726 }
727 } else if (issjishwkana(*in)) {
728 *out++ = SS2;
729 *out++ = *in++;
730 } else if (issjisext(*in)) {
731 error_ = -1;
732 *out++ = SUBST_CHAR;
733 in++;
734 if (*in != '\0' && !isascii(*in)) {
735 *out++ = SUBST_CHAR;
736 in++;
737 }
738 } else {
739 error_ = -1;
740 *out++ = SUBST_CHAR;
741 in++;
742 }
743 }
744
745 *out = '\0';
746
747 if (error)
748 *error = error_;
749
750 return outbuf;
751}
752
753static gchar *conv_jistoutf8(const gchar *inbuf, gint *error)
754{
755 gchar *tmpstr, *utf8str;
756 gint t_error = 0, u_error = 0;
757
758 if (strstr(inbuf, "\033$(D")) {
759 tmpstr = conv_jistoeuc(inbuf, &t_error);
760 utf8str = conv_euctoutf8(tmpstr, &u_error);
761 } else {
762 tmpstr = conv_jistosjis(inbuf, &t_error);
763 utf8str = conv_sjistoutf8(tmpstr, &u_error);
764 }
765 g_free(tmpstr);
766
767 if (error)
768 *error = (t_error | u_error);
769
770 return utf8str;
771}
772
773#if USE_THREADS
774#define S_LOCK_DEFINE_STATIC(name) G_LOCK_DEFINE_STATIC(name)
775#define S_LOCK(name) G_LOCK(name)
776#define S_UNLOCK(name) G_UNLOCK(name)
777#else
778#define S_LOCK_DEFINE_STATIC(name)
779#define S_LOCK(name)
780#define S_UNLOCK(name)
781#endif
782
783static gchar *conv_sjistoutf8(const gchar *inbuf, gint *error)
784{
785 static iconv_t cd = (iconv_t)-1;
786 static gboolean iconv_ok = TRUE;
787 S_LOCK_DEFINE_STATIC(cd);
788 gchar *ret;
789
790 S_LOCK(cd);
791
792 if (cd == (iconv_t)-1) {
793 if (!iconv_ok) {
794 S_UNLOCK(cd);
795 if (error)
796 *error = -1;
797 return g_strdup(inbuf);
798 }
799
800 cd = iconv_open(CS_UTF_8, CS_CP932);
801 if (cd == (iconv_t)-1) {
802 cd = iconv_open(CS_UTF_8, CS_SHIFT_JIS);
803 if (cd == (iconv_t)-1) {
804 g_warning("conv_sjistoutf8(): %s\n",
805 g_strerror(errno));
806 iconv_ok = FALSE;
807 S_UNLOCK(cd);
808 if (error)
809 *error = -1;
810 return g_strdup(inbuf);
811 }
812 }
813 }
814
815 ret = conv_iconv_strdup_with_cd(inbuf, cd, error);
816 S_UNLOCK(cd);
817 return ret;
818}
819
820static gchar *conv_euctoutf8(const gchar *inbuf, gint *error)
821{
822 static iconv_t cd = (iconv_t)-1;
823 static gboolean iconv_ok = TRUE;
824 S_LOCK_DEFINE_STATIC(cd);
825 gchar *ret;
826
827 S_LOCK(cd);
828
829 if (cd == (iconv_t)-1) {
830 if (!iconv_ok) {
831 S_UNLOCK(cd);
832 if (error)
833 *error = -1;
834 return g_strdup(inbuf);
835 }
836
837 cd = iconv_open(CS_UTF_8, CS_EUC_JP_MS);
838 if (cd == (iconv_t)-1) {
839 cd = iconv_open(CS_UTF_8, CS_EUC_JP);
840 if (cd == (iconv_t)-1) {
841 g_warning("conv_euctoutf8(): %s\n",
842 g_strerror(errno));
843 iconv_ok = FALSE;
844 S_UNLOCK(cd);
845 if (error)
846 *error = -1;
847 return g_strdup(inbuf);
848 }
849 }
850 }
851
852 ret = conv_iconv_strdup_with_cd(inbuf, cd, error);
853 S_UNLOCK(cd);
854 return ret;
855}
856
857static gchar *conv_anytoutf8(const gchar *inbuf, gint *error)
858{
859 switch (conv_guess_ja_encoding(inbuf)) {
860 case C_ISO_2022_JP:
861 return conv_jistoutf8(inbuf, error);
862 case C_SHIFT_JIS:
863 return conv_sjistoutf8(inbuf, error);
864 case C_EUC_JP:
865 return conv_euctoutf8(inbuf, error);
866 case C_UTF_8:
867 if (error)
868 *error = 0;
869 if (isutf8bom(inbuf))
870 inbuf += 3;
871 return g_strdup(inbuf);
872 default:
873 if (error)
874 *error = 0;
875 return g_strdup(inbuf);
876 }
877}
878
879static gchar *conv_utf8tosjis(const gchar *inbuf, gint *error)
880{
881 static iconv_t cd = (iconv_t)-1;
882 static gboolean iconv_ok = TRUE;
883 S_LOCK_DEFINE_STATIC(cd);
884 gchar *ret;
885
886 S_LOCK(cd);
887
888 if (cd == (iconv_t)-1) {
889 if (!iconv_ok) {
890 S_UNLOCK(cd);
891 if (error)
892 *error = -1;
893 return g_strdup(inbuf);
894 }
895
896 cd = iconv_open(CS_CP932, CS_UTF_8);
897 if (cd == (iconv_t)-1) {
898 cd = iconv_open(CS_SHIFT_JIS, CS_UTF_8);
899 if (cd == (iconv_t)-1) {
900 g_warning("conv_utf8tosjis(): %s\n",
901 g_strerror(errno));
902 iconv_ok = FALSE;
903 S_UNLOCK(cd);
904 if (error)
905 *error = -1;
906 return g_strdup(inbuf);
907 }
908 }
909 }
910
911 if (isutf8bom(inbuf))
912 inbuf += 3;
913 ret = conv_iconv_strdup_with_cd(inbuf, cd, error);
914 S_UNLOCK(cd);
915 return ret;
916}
917
918static gchar *conv_utf8toeuc(const gchar *inbuf, gint *error)
919{
920 static iconv_t cd = (iconv_t)-1;
921 static gboolean iconv_ok = TRUE;
922 S_LOCK_DEFINE_STATIC(cd);
923 gchar *ret;
924
925 S_LOCK(cd);
926
927 if (cd == (iconv_t)-1) {
928 if (!iconv_ok) {
929 S_UNLOCK(cd);
930 if (error)
931 *error = -1;
932 return g_strdup(inbuf);
933 }
934
935 cd = iconv_open(CS_EUC_JP_MS, CS_UTF_8);
936 if (cd == (iconv_t)-1) {
937 cd = iconv_open(CS_EUC_JP, CS_UTF_8);
938 if (cd == (iconv_t)-1) {
939 g_warning("conv_utf8toeuc(): %s\n",
940 g_strerror(errno));
941 iconv_ok = FALSE;
942 S_UNLOCK(cd);
943 if (error)
944 *error = -1;
945 return g_strdup(inbuf);
946 }
947 }
948 }
949
950 if (isutf8bom(inbuf))
951 inbuf += 3;
952 ret = conv_iconv_strdup_with_cd(inbuf, cd, error);
953 S_UNLOCK(cd);
954 return ret;
955}
956
957static gchar *conv_utf8tojis(const gchar *inbuf, gint *error)
958{
959 gchar *tmpstr, *jisstr;
960 gint t_error = 0, j_error = 0;
961
962#if 1
963 tmpstr = conv_utf8tosjis(inbuf, &t_error);
964 jisstr = conv_sjistojis(tmpstr, &j_error);
965#else
966 tmpstr = conv_utf8toeuc(inbuf, &t_error);
967 jisstr = conv_euctojis(tmpstr, &j_error);
968#endif
969 g_free(tmpstr);
970
971 if (error)
972 *error = (t_error | j_error);
973
974 return jisstr;
975}
976
977#if 0
978static gchar valid_eucjp_tbl[][96] = {
979 /* 0xa2a0 - 0xa2ff */
980 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
981 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
982 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
983 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
984 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
985 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0 },
986
987 /* 0xa3a0 - 0xa3ff */
988 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
989 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
990 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
991 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
992 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
993 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 },
994
995 /* 0xa4a0 - 0xa4ff */
996 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
997 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
998 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
999 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1000 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1001 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
1002
1003 /* 0xa5a0 - 0xa5ff */
1004 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1005 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1006 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1007 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1008 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1009 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
1010
1011 /* 0xa6a0 - 0xa6ff */
1012 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1013 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
1014 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1015 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
1016 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1017 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
1018
1019 /* 0xa7a0 - 0xa7ff */
1020 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1021 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1022 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1023 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1024 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1025 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
1026
1027 /* 0xa8a0 - 0xa8ff */
1028 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1029 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1030 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1031 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1032 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1033 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
1034};
1035
1036static gboolean isprintableeuckanji(guchar c1, guchar c2)
1037{
1038 if (c1 <= 0xa0 || c1 >= 0xf5)
1039 return FALSE;
1040 if (c2 <= 0xa0 || c2 == 0xff)
1041 return FALSE;
1042
1043 if (c1 >= 0xa9 && c1 <= 0xaf)
1044 return FALSE;
1045
1046 if (c1 >= 0xa2 && c1 <= 0xa8)
1047 return (gboolean)valid_eucjp_tbl[c1 - 0xa2][c2 - 0xa0];
1048
1049 if (c1 == 0xcf) {
1050 if (c2 >= 0xd4 && c2 <= 0xfe)
1051 return FALSE;
1052 } else if (c1 == 0xf4) {
1053 if (c2 >= 0xa7 && c2 <= 0xfe)
1054 return FALSE;
1055 }
1056
1057 return TRUE;
1058}
1059
1060static void conv_unreadable_eucjp(gchar *str)
1061{
1062 register guchar *p = str;
1063
1064 while (*p != '\0') {
1065 if (isascii(*p)) {
1066 /* convert CR+LF -> LF */
1067 if (*p == '\r' && *(p + 1) == '\n')
1068 memmove(p, p + 1, strlen(p));
1069 /* printable 7 bit code */
1070 p++;
1071 } else if (iseuckanji(*p)) {
1072 if (isprintableeuckanji(*p, *(p + 1))) {
1073 /* printable euc-jp code */
1074 p += 2;
1075 } else {
1076 /* substitute unprintable code */
1077 *p++ = SUBST_CHAR;
1078 if (*p != '\0') {
1079 if (isascii(*p))
1080 p++;
1081 else
1082 *p++ = SUBST_CHAR;
1083 }
1084 }
1085 } else if (iseuchwkana1(*p)) {
1086 if (iseuchwkana2(*(p + 1)))
1087 /* euc-jp hankaku kana */
1088 p += 2;
1089 else
1090 *p++ = SUBST_CHAR;
1091 } else if (iseucaux(*p)) {
1092 if (iseuckanji(*(p + 1)) && iseuckanji(*(p + 2))) {
1093 /* auxiliary kanji */
1094 p += 3;
1095 } else
1096 *p++ = SUBST_CHAR;
1097 } else
1098 /* substitute unprintable 1 byte code */
1099 *p++ = SUBST_CHAR;
1100 }
1101}
1102#endif
1103
1104static void conv_unreadable_8bit(gchar *str)
1105{
1106 register gchar *p = str;
1107
1108 while (*p != '\0') {
1109 /* convert CR+LF -> LF */
1110 if (*p == '\r' && *(p + 1) == '\n')
1111 memmove(p, p + 1, strlen(p));
1112 else if (!isascii(*(guchar *)p)) *p = SUBST_CHAR;
1113 p++;
1114 }
1115}
1116
1117#if 0
1118static void conv_unreadable_latin(gchar *str)
1119{
1120 register guchar *p = str;
1121
1122 while (*p != '\0') {
1123 /* convert CR+LF -> LF */
1124 if (*p == '\r' && *(p + 1) == '\n')
1125 memmove(p, p + 1, strlen(p));
1126 else if ((*p & 0xff) >= 0x7f && (*p & 0xff) <= 0x9f)
1127 *p = SUBST_CHAR;
1128 p++;
1129 }
1130}
1131#endif
1132
1133#define NCV '\0'
1134
1135void conv_mb_alnum(gchar *str)
1136{
1137 static guchar char_tbl[] = {
1138 /* 0xa0 - 0xaf */
1139 NCV, ' ', NCV, NCV, ',', '.', NCV, ':',
1140 ';', '?', '!', NCV, NCV, NCV, NCV, NCV,
1141 /* 0xb0 - 0xbf */
1142 NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV,
1143 NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV,
1144 /* 0xc0 - 0xcf */
1145 NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV,
1146 NCV, NCV, '(', ')', NCV, NCV, '[', ']',
1147 /* 0xd0 - 0xdf */
1148 '{', '}', NCV, NCV, NCV, NCV, NCV, NCV,
1149 NCV, NCV, NCV, NCV, '+', '-', NCV, NCV,
1150 /* 0xe0 - 0xef */
1151 NCV, '=', NCV, '<', '>', NCV, NCV, NCV,
1152 NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV
1153 };
1154
1155 register guchar *p = (guchar *)str;
1156 register gint len;
1157
1158 len = strlen(str);
1159
1160 while (len > 1) {
1161 if (*p == 0xa3) {
1162 register guchar ch = *(p + 1);
1163
1164 if (ch >= 0xb0 && ch <= 0xfa) {
1165 /* [a-zA-Z] */
1166 *p = ch & 0x7f;
1167 p++;
1168 len--;
1169 memmove(p, p + 1, len);
1170 len--;
1171 } else {
1172 p += 2;
1173 len -= 2;
1174 }
1175 } else if (*p == 0xa1) {
1176 register guchar ch = *(p + 1);
1177
1178 if (ch >= 0xa0 && ch <= 0xef &&
1179 NCV != char_tbl[ch - 0xa0]) {
1180 *p = char_tbl[ch - 0xa0];
1181 p++;
1182 len--;
1183 memmove(p, p + 1, len);
1184 len--;
1185 } else {
1186 p += 2;
1187 len -= 2;
1188 }
1189 } else if (iseuckanji(*p)) {
1190 p += 2;
1191 len -= 2;
1192 } else {
1193 p++;
1194 len--;
1195 }
1196 }
1197}
1198
1199CharSet conv_guess_ja_encoding(const gchar *str)
1200{
1201 const guchar *p = (const guchar *)str;
1202 CharSet guessed = C_US_ASCII;
1203
1204 while (*p != '\0') {
1205 if (*p == ESC && (*(p + 1) == '$' || *(p + 1) == '(')) {
1206 if (guessed == C_US_ASCII)
1207 return C_ISO_2022_JP;
1208 p += 2;
1209 } else if (isascii(*p)) {
1210 p++;
1211 } else if (iseuckanji(*p) && iseuckanji(*(p + 1))) {
1212 if (*p >= 0xfd && *p <= 0xfe)
1213 return C_EUC_JP;
1214 else if (guessed == C_SHIFT_JIS) {
1215 if ((issjiskanji1(*p) &&
1216 issjiskanji2(*(p + 1))) ||
1217 issjishwkana(*p))
1218 guessed = C_SHIFT_JIS;
1219 else
1220 guessed = C_EUC_JP;
1221 } else
1222 guessed = C_EUC_JP;
1223 p += 2;
1224 } else if (issjiskanji1(*p) && issjiskanji2(*(p + 1))) {
1225 guessed = C_SHIFT_JIS;
1226 p += 2;
1227 } else if (issjishwkana(*p)) {
1228 guessed = C_SHIFT_JIS;
1229 p++;
1230 } else {
1231 if (guessed == C_US_ASCII)
1232 guessed = C_AUTO;
1233 p++;
1234 }
1235 }
1236
1237 if (guessed != C_US_ASCII) {
1238 p = (const guchar *)str;
1239
1240 while (*p != '\0') {
1241 if (isascii(*p)) {
1242 p++;
1243 } else if (isutf8_3_1(*p) &&
1244 isutf8_3_2(*(p + 1)) &&
1245 isutf8_3_2(*(p + 2))) {
1246 p += 3;
1247 } else {
1248 return guessed;
1249 }
1250 }
1251
1252 return C_UTF_8;
1253 }
1254
1255 return guessed;
1256}
1257
1258static gchar *conv_jistodisp(const gchar *inbuf, gint *error)
1259{
1260 return conv_jistoutf8(inbuf, error);
1261}
1262
1263static gchar *conv_sjistodisp(const gchar *inbuf, gint *error)
1264{
1265 return conv_sjistoutf8(inbuf, error);
1266}
1267
1268static gchar *conv_euctodisp(const gchar *inbuf, gint *error)
1269{
1270 return conv_euctoutf8(inbuf, error);
1271}
1272
1273gchar *conv_utf8todisp(const gchar *inbuf, gint *error)
1274{
1275 if (g_utf8_validate(inbuf, -1, NULL) == TRUE) {
1276 if (error)
1277 *error = 0;
1278 if (isutf8bom(inbuf))
1279 inbuf += 3;
1280 return g_strdup(inbuf);
1281 } else
1282 return conv_ustodisp(inbuf, error);
1283}
1284
1285static gchar *conv_anytodisp(const gchar *inbuf, gint *error)
1286{
1287 gchar *outbuf;
1288
1289 outbuf = conv_anytoutf8(inbuf, error);
1290 if (g_utf8_validate(outbuf, -1, NULL) != TRUE) {
1291 if (error)
1292 *error = -1;
1293 conv_unreadable_8bit(outbuf);
1294 }
1295
1296 return outbuf;
1297}
1298
1299static gchar *conv_ustodisp(const gchar *inbuf, gint *error)
1300{
1301 gchar *outbuf;
1302
1303 outbuf = g_strdup(inbuf);
1304 conv_unreadable_8bit(outbuf);
1305 if (error)
1306 *error = 0;
1307
1308 return outbuf;
1309}
1310
1311gchar *conv_localetodisp(const gchar *inbuf, gint *error)
1312{
1313 gchar *str;
1314
1315 str = conv_iconv_strdup(inbuf, conv_get_locale_charset_str(),
1316 CS_INTERNAL, error);
1317 if (!str)
1318 str = conv_utf8todisp(inbuf, NULL);
1319
1320 return str;
1321}
1322
1323static gchar *conv_noconv(const gchar *inbuf, gint *error)
1324{
1325 if (error)
1326 *error = 0;
1327 return g_strdup(inbuf);
1328}
1329
1330static const gchar *
1331conv_get_fallback_for_private_encoding(const gchar *encoding)
1332{
1333 if (encoding) {
1334 if ((encoding[0] == 'X' || encoding[0] == 'x') &&
1335 encoding[1] == '-') {
1336 if (!g_ascii_strcasecmp(encoding, CS_X_GBK))
1337 return CS_GBK;
1338 else if (!g_ascii_strcasecmp(encoding, CS_X_SJIS))
1339 return CS_SHIFT_JIS;
1340 } else if ((encoding[0] == 'K' || encoding[0] == 'k') &&
1341 (encoding[1] == 'S' || encoding[1] == 's')) {
1342 if (!g_ascii_strcasecmp(encoding, CS_KS_C_5601_1987))
1343 return CS_EUC_KR;
1344 }
1345 }
1346
1347 return encoding;
1348}
1349
1350CodeConverter *conv_code_converter_new(const gchar *src_encoding,
1351 const gchar *dest_encoding)
1352{
1353 CodeConverter *conv;
1354
1355 src_encoding = conv_get_fallback_for_private_encoding(src_encoding);
1356
1357 conv = g_new0(CodeConverter, 1);
1358 conv->code_conv_func =
1359 conv_get_code_conv_func(src_encoding, dest_encoding);
1360 conv->src_encoding = g_strdup(src_encoding);
1361 conv->dest_encoding = g_strdup(dest_encoding);
1362
1363 return conv;
1364}
1365
1366void conv_code_converter_destroy(CodeConverter *conv)
1367{
1368 g_free(conv->src_encoding);
1369 g_free(conv->dest_encoding);
1370 g_free(conv);
1371}
1372
1373gchar *conv_convert(CodeConverter *conv, const gchar *inbuf)
1374{
1375 if (!inbuf)
1376 return NULL;
1377 else if (conv->code_conv_func != conv_noconv)
1378 return conv->code_conv_func(inbuf, NULL);
1379 else
1380 return conv_iconv_strdup
1381 (inbuf, conv->src_encoding, conv->dest_encoding, NULL);
1382}
1383
1384gchar *conv_codeset_strdup_full(const gchar *inbuf,
1385 const gchar *src_encoding,
1386 const gchar *dest_encoding,
1387 gint *error)
1388{
1389 CodeConvFunc conv_func;
1390
1391 if (!inbuf) {
1392 if (error)
1393 *error = 0;
1394 return NULL;
1395 }
1396
1397 src_encoding = conv_get_fallback_for_private_encoding(src_encoding);
1398
1399 conv_func = conv_get_code_conv_func(src_encoding, dest_encoding);
1400 if (conv_func != conv_noconv)
1401 return conv_func(inbuf, error);
1402
1403 return conv_iconv_strdup(inbuf, src_encoding, dest_encoding, error);
1404}
1405
1406CodeConvFunc conv_get_code_conv_func(const gchar *src_encoding,
1407 const gchar *dest_encoding)
1408{
1409 CodeConvFunc code_conv = conv_noconv;
1410 CharSet src_charset;
1411 CharSet dest_charset;
1412
1413 if (!src_encoding)
1414 src_charset = conv_get_locale_charset();
1415 else
1416 src_charset = conv_get_charset_from_str(src_encoding);
1417
1418 /* auto detection mode */
1419 if (!src_encoding && !dest_encoding) {
1420 if (conv_ad_type == C_AD_JAPANESE ||
1421 (conv_ad_type == C_AD_BY_LOCALE && conv_is_ja_locale()))
1422 return conv_anytodisp;
1423 else
1424 return conv_noconv;
1425 }
1426
1427 dest_charset = conv_get_charset_from_str(dest_encoding);
1428
1429 if (dest_charset == C_US_ASCII)
1430 return conv_ustodisp;
1431
1432 switch (src_charset) {
1433 case C_US_ASCII:
1434 case C_ISO_8859_1:
1435 case C_ISO_8859_2:
1436 case C_ISO_8859_3:
1437 case C_ISO_8859_4:
1438 case C_ISO_8859_5:
1439 case C_ISO_8859_6:
1440 case C_ISO_8859_7:
1441 case C_ISO_8859_8:
1442 case C_ISO_8859_9:
1443 case C_ISO_8859_10:
1444 case C_ISO_8859_11:
1445 case C_ISO_8859_13:
1446 case C_ISO_8859_14:
1447 case C_ISO_8859_15:
1448 case C_ISO_8859_16:
1449 break;
1450 case C_ISO_2022_JP:
1451 case C_ISO_2022_JP_2:
1452 case C_ISO_2022_JP_3:
1453 if (dest_charset == C_AUTO)
1454 code_conv = conv_jistodisp;
1455 else if (dest_charset == C_EUC_JP)
1456 code_conv = conv_jistoeuc;
1457 else if (dest_charset == C_SHIFT_JIS ||
1458 dest_charset == C_CP932)
1459 code_conv = conv_jistosjis;
1460 else if (dest_charset == C_UTF_8)
1461 code_conv = conv_jistoutf8;
1462 break;
1463 case C_SHIFT_JIS:
1464 case C_CP932:
1465 if (dest_charset == C_AUTO)
1466 code_conv = conv_sjistodisp;
1467 else if (dest_charset == C_ISO_2022_JP ||
1468 dest_charset == C_ISO_2022_JP_2 ||
1469 dest_charset == C_ISO_2022_JP_3)
1470 code_conv = conv_sjistojis;
1471 else if (dest_charset == C_EUC_JP)
1472 code_conv = conv_sjistoeuc;
1473 else if (dest_charset == C_UTF_8)
1474 code_conv = conv_sjistoutf8;
1475 break;
1476 case C_EUC_JP:
1477 if (dest_charset == C_AUTO)
1478 code_conv = conv_euctodisp;
1479 else if (dest_charset == C_ISO_2022_JP ||
1480 dest_charset == C_ISO_2022_JP_2 ||
1481 dest_charset == C_ISO_2022_JP_3)
1482 code_conv = conv_euctojis;
1483 else if (dest_charset == C_UTF_8)
1484 code_conv = conv_euctoutf8;
1485 break;
1486 case C_UTF_8:
1487 if (dest_charset == C_EUC_JP)
1488 code_conv = conv_utf8toeuc;
1489 else if (dest_charset == C_ISO_2022_JP ||
1490 dest_charset == C_ISO_2022_JP_2 ||
1491 dest_charset == C_ISO_2022_JP_3)
1492 code_conv = conv_utf8tojis;
1493 else if (dest_charset == C_SHIFT_JIS ||
1494 dest_charset == C_CP932)
1495 code_conv = conv_utf8tosjis;
1496 break;
1497 default:
1498 break;
1499 }
1500
1501 return code_conv;
1502}
1503
1504gchar *conv_iconv_strdup(const gchar *inbuf,
1505 const gchar *src_code, const gchar *dest_code,
1506 gint *error)
1507{
1508 iconv_t cd;
1509 gchar *outbuf;
1510
1511 if (!src_code)
1512 src_code = conv_get_locale_charset_str();
1513 if (!dest_code)
1514 dest_code = CS_INTERNAL;
1515
1516 cd = iconv_open(dest_code, src_code);
1517 if (cd == (iconv_t)-1) {
1518 if (error)
1519 *error = -1;
1520 return NULL;
1521 }
1522
1523 outbuf = conv_iconv_strdup_with_cd(inbuf, cd, error);
1524
1525 iconv_close(cd);
1526
1527 return outbuf;
1528}
1529
1530gchar *conv_iconv_strdup_with_cd(const gchar *inbuf, iconv_t cd, gint *error)
1531{
1532 const gchar *inbuf_p;
1533 gchar *outbuf;
1534 gchar *outbuf_p;
1535 size_t in_size;
1536 size_t in_left;
1537 size_t out_size;
1538 size_t out_left;
1539 size_t n_conv;
1540 size_t len;
1541 gint error_ = 0;
1542
1543 if (!inbuf) {
1544 if (error)
1545 *error = 0;
1546 return NULL;
1547 }
1548
1549 inbuf_p = inbuf;
1550 in_size = strlen(inbuf);
1551 in_left = in_size;
1552 out_size = (in_size + 1) * 2;
1553 outbuf = g_malloc(out_size);
1554 outbuf_p = outbuf;
1555 out_left = out_size;
1556
1557#define EXPAND_BUF() \
1558{ \
1559 len = outbuf_p - outbuf; \
1560 out_size *= 2; \
1561 outbuf = g_realloc(outbuf, out_size); \
1562 outbuf_p = outbuf + len; \
1563 out_left = out_size - len; \
1564}
1565
1566 while ((n_conv = iconv(cd, (ICONV_CONST gchar **)&inbuf_p, &in_left,
1567 &outbuf_p, &out_left)) == (size_t)-1) {
1568 if (EILSEQ == errno) {
1569 /* g_print("iconv(): at %d: %s\n", in_size - in_left, g_strerror(errno)); */
1570 error_ = -1;
1571 inbuf_p++;
1572 in_left--;
1573 if (out_left == 0) {
1574 EXPAND_BUF();
1575 }
1576 *outbuf_p++ = SUBST_CHAR;
1577 out_left--;
1578 } else if (EINVAL == errno) {
1579 error_ = -1;
1580 break;
1581 } else if (E2BIG == errno) {
1582 EXPAND_BUF();
1583 } else {
1584 g_warning("conv_iconv_strdup(): %s\n",
1585 g_strerror(errno));
1586 error_ = -1;
1587 break;
1588 }
1589 }
1590
1591 while ((n_conv = iconv(cd, NULL, NULL, &outbuf_p, &out_left)) ==
1592 (size_t)-1) {
1593 if (E2BIG == errno) {
1594 EXPAND_BUF();
1595 } else {
1596 g_warning("conv_iconv_strdup(): %s\n",
1597 g_strerror(errno));
1598 error_ = -1;
1599 break;
1600 }
1601 }
1602
1603#undef EXPAND_BUF
1604
1605 len = outbuf_p - outbuf;
1606 outbuf = g_realloc(outbuf, len + 1);
1607 outbuf[len] = '\0';
1608
1609 if (error)
1610 *error = error_;
1611
1612 return outbuf;
1613}
1614
1615static const struct {
1616 CharSet charset;
1617 gchar *const name;
1618} charsets[] = {
1619 {C_US_ASCII, CS_US_ASCII},
1620 {C_US_ASCII, CS_ANSI_X3_4_1968},
1621 {C_UTF_8, CS_UTF_8},
1622 {C_UTF_7, CS_UTF_7},
1623 {C_ISO_8859_1, CS_ISO_8859_1},
1624 {C_ISO_8859_2, CS_ISO_8859_2},
1625 {C_ISO_8859_3, CS_ISO_8859_3},
1626 {C_ISO_8859_4, CS_ISO_8859_4},
1627 {C_ISO_8859_5, CS_ISO_8859_5},
1628 {C_ISO_8859_6, CS_ISO_8859_6},
1629 {C_ISO_8859_7, CS_ISO_8859_7},
1630 {C_ISO_8859_8, CS_ISO_8859_8},
1631 {C_ISO_8859_9, CS_ISO_8859_9},
1632 {C_ISO_8859_10, CS_ISO_8859_10},
1633 {C_ISO_8859_11, CS_ISO_8859_11},
1634 {C_ISO_8859_13, CS_ISO_8859_13},
1635 {C_ISO_8859_14, CS_ISO_8859_14},
1636 {C_ISO_8859_15, CS_ISO_8859_15},
1637 {C_BALTIC, CS_BALTIC},
1638 {C_CP932, CS_CP932},
1639 {C_CP1250, CS_CP1250},
1640 {C_CP1251, CS_CP1251},
1641 {C_CP1252, CS_CP1252},
1642 {C_CP1253, CS_CP1253},
1643 {C_CP1254, CS_CP1254},
1644 {C_CP1255, CS_CP1255},
1645 {C_CP1256, CS_CP1256},
1646 {C_CP1257, CS_CP1257},
1647 {C_CP1258, CS_CP1258},
1648 {C_WINDOWS_932, CS_WINDOWS_932},
1649 {C_WINDOWS_1250, CS_WINDOWS_1250},
1650 {C_WINDOWS_1251, CS_WINDOWS_1251},
1651 {C_WINDOWS_1252, CS_WINDOWS_1252},
1652 {C_WINDOWS_1253, CS_WINDOWS_1253},
1653 {C_WINDOWS_1254, CS_WINDOWS_1254},
1654 {C_WINDOWS_1255, CS_WINDOWS_1255},
1655 {C_WINDOWS_1256, CS_WINDOWS_1256},
1656 {C_WINDOWS_1257, CS_WINDOWS_1257},
1657 {C_WINDOWS_1258, CS_WINDOWS_1258},
1658 {C_KOI8_R, CS_KOI8_R},
1659 {C_KOI8_T, CS_KOI8_T},
1660 {C_KOI8_U, CS_KOI8_U},
1661 {C_ISO_2022_JP, CS_ISO_2022_JP},
1662 {C_ISO_2022_JP_2, CS_ISO_2022_JP_2},
1663 {C_ISO_2022_JP_3, CS_ISO_2022_JP_3},
1664 {C_EUC_JP, CS_EUC_JP},
1665 {C_EUC_JP, CS_EUCJP},
1666 {C_EUC_JP_MS, CS_EUC_JP_MS},
1667 {C_SHIFT_JIS, CS_SHIFT_JIS},
1668 {C_SHIFT_JIS, CS_SHIFT__JIS},
1669 {C_SHIFT_JIS, CS_SJIS},
1670 {C_ISO_2022_KR, CS_ISO_2022_KR},
1671 {C_EUC_KR, CS_EUC_KR},
1672 {C_ISO_2022_CN, CS_ISO_2022_CN},
1673 {C_EUC_CN, CS_EUC_CN},
1674 {C_GB2312, CS_GB2312},
1675 {C_GBK, CS_GBK},
1676 {C_EUC_TW, CS_EUC_TW},
1677 {C_BIG5, CS_BIG5},
1678 {C_BIG5_HKSCS, CS_BIG5_HKSCS},
1679 {C_TIS_620, CS_TIS_620},
1680 {C_WINDOWS_874, CS_WINDOWS_874},
1681 {C_GEORGIAN_PS, CS_GEORGIAN_PS},
1682 {C_TCVN5712_1, CS_TCVN5712_1},
1683 {C_ISO_8859_16, CS_ISO_8859_16},
1684 {C_UTF_16, CS_UTF_16},
1685 {C_UTF_16BE, CS_UTF_16BE},
1686 {C_UTF_16LE, CS_UTF_16LE},
1687};
1688
1689static const struct {
1690 gchar *const locale;
1691 CharSet charset;
1692 CharSet out_charset;
1693} locale_table[] = {
1694 {"ja_JP.eucJP" , C_EUC_JP , C_ISO_2022_JP},
1695 {"ja_JP.EUC-JP" , C_EUC_JP , C_ISO_2022_JP},
1696 {"ja_JP.EUC" , C_EUC_JP , C_ISO_2022_JP},
1697 {"ja_JP.ujis" , C_EUC_JP , C_ISO_2022_JP},
1698 {"ja_JP.SJIS" , C_SHIFT_JIS , C_ISO_2022_JP},
1699 {"ja_JP.JIS" , C_ISO_2022_JP , C_ISO_2022_JP},
1700#ifdef G_OS_WIN32
1701 {"ja_JP" , C_CP932 , C_ISO_2022_JP},
1702#elif defined(__APPLE__)
1703 {"ja_JP" , C_UTF_8 , C_ISO_2022_JP},
1704#else
1705 {"ja_JP" , C_EUC_JP , C_ISO_2022_JP},
1706#endif
1707 {"ko_KR.EUC-KR" , C_EUC_KR , C_EUC_KR},
1708 {"ko_KR" , C_EUC_KR , C_EUC_KR},
1709 {"zh_CN.GB2312" , C_GB2312 , C_GB2312},
1710 {"zh_CN.GBK" , C_GBK , C_GBK},
1711 {"zh_CN" , C_GB2312 , C_GB2312},
1712 {"zh_HK" , C_BIG5_HKSCS , C_BIG5_HKSCS},
1713 {"zh_TW.eucTW" , C_EUC_TW , C_BIG5},
1714 {"zh_TW.EUC-TW" , C_EUC_TW , C_BIG5},
1715 {"zh_TW.Big5" , C_BIG5 , C_BIG5},
1716 {"zh_TW" , C_BIG5 , C_BIG5},
1717
1718 {"ru_RU.KOI8-R" , C_KOI8_R , C_KOI8_R},
1719 {"ru_RU.KOI8R" , C_KOI8_R , C_KOI8_R},
1720 {"ru_RU.CP1251" , C_WINDOWS_1251, C_KOI8_R},
1721 {"ru_RU" , C_ISO_8859_5 , C_KOI8_R},
1722 {"tg_TJ" , C_KOI8_T , C_KOI8_T},
1723 {"ru_UA" , C_KOI8_U , C_KOI8_U},
1724 {"uk_UA.CP1251" , C_WINDOWS_1251, C_KOI8_U},
1725 {"uk_UA" , C_KOI8_U , C_KOI8_U},
1726
1727 {"be_BY" , C_WINDOWS_1251, C_WINDOWS_1251},
1728 {"bg_BG" , C_WINDOWS_1251, C_WINDOWS_1251},
1729
1730 {"yi_US" , C_WINDOWS_1255, C_WINDOWS_1255},
1731
1732 {"af_ZA" , C_ISO_8859_1 , C_ISO_8859_1},
1733 {"br_FR" , C_ISO_8859_1 , C_ISO_8859_1},
1734 {"ca_ES" , C_ISO_8859_1 , C_ISO_8859_1},
1735 {"da_DK" , C_ISO_8859_1 , C_ISO_8859_1},
1736 {"de_AT" , C_ISO_8859_1 , C_ISO_8859_1},
1737 {"de_BE" , C_ISO_8859_1 , C_ISO_8859_1},
1738 {"de_CH" , C_ISO_8859_1 , C_ISO_8859_1},
1739 {"de_DE" , C_ISO_8859_1 , C_ISO_8859_1},
1740 {"de_LU" , C_ISO_8859_1 , C_ISO_8859_1},
1741 {"en_AU" , C_ISO_8859_1 , C_ISO_8859_1},
1742 {"en_BW" , C_ISO_8859_1 , C_ISO_8859_1},
1743 {"en_CA" , C_ISO_8859_1 , C_ISO_8859_1},
1744 {"en_DK" , C_ISO_8859_1 , C_ISO_8859_1},
1745 {"en_GB" , C_ISO_8859_1 , C_ISO_8859_1},
1746 {"en_HK" , C_ISO_8859_1 , C_ISO_8859_1},
1747 {"en_IE" , C_ISO_8859_1 , C_ISO_8859_1},
1748 {"en_NZ" , C_ISO_8859_1 , C_ISO_8859_1},
1749 {"en_PH" , C_ISO_8859_1 , C_ISO_8859_1},
1750 {"en_SG" , C_ISO_8859_1 , C_ISO_8859_1},
1751 {"en_US" , C_ISO_8859_1 , C_ISO_8859_1},
1752 {"en_ZA" , C_ISO_8859_1 , C_ISO_8859_1},
1753 {"en_ZW" , C_ISO_8859_1 , C_ISO_8859_1},
1754 {"es_AR" , C_ISO_8859_1 , C_ISO_8859_1},
1755 {"es_BO" , C_ISO_8859_1 , C_ISO_8859_1},
1756 {"es_CL" , C_ISO_8859_1 , C_ISO_8859_1},
1757 {"es_CO" , C_ISO_8859_1 , C_ISO_8859_1},
1758 {"es_CR" , C_ISO_8859_1 , C_ISO_8859_1},
1759 {"es_DO" , C_ISO_8859_1 , C_ISO_8859_1},
1760 {"es_EC" , C_ISO_8859_1 , C_ISO_8859_1},
1761 {"es_ES" , C_ISO_8859_1 , C_ISO_8859_1},
1762 {"es_GT" , C_ISO_8859_1 , C_ISO_8859_1},
1763 {"es_HN" , C_ISO_8859_1 , C_ISO_8859_1},
1764 {"es_MX" , C_ISO_8859_1 , C_ISO_8859_1},
1765 {"es_NI" , C_ISO_8859_1 , C_ISO_8859_1},
1766 {"es_PA" , C_ISO_8859_1 , C_ISO_8859_1},
1767 {"es_PE" , C_ISO_8859_1 , C_ISO_8859_1},
1768 {"es_PR" , C_ISO_8859_1 , C_ISO_8859_1},
1769 {"es_PY" , C_ISO_8859_1 , C_ISO_8859_1},
1770 {"es_SV" , C_ISO_8859_1 , C_ISO_8859_1},
1771 {"es_US" , C_ISO_8859_1 , C_ISO_8859_1},
1772 {"es_UY" , C_ISO_8859_1 , C_ISO_8859_1},
1773 {"es_VE" , C_ISO_8859_1 , C_ISO_8859_1},
1774 {"et_EE" , C_ISO_8859_1 , C_ISO_8859_1},
1775 {"eu_ES" , C_ISO_8859_1 , C_ISO_8859_1},
1776 {"fi_FI" , C_ISO_8859_1 , C_ISO_8859_1},
1777 {"fo_FO" , C_ISO_8859_1 , C_ISO_8859_1},
1778 {"fr_BE" , C_ISO_8859_1 , C_ISO_8859_1},
1779 {"fr_CA" , C_ISO_8859_1 , C_ISO_8859_1},
1780 {"fr_CH" , C_ISO_8859_1 , C_ISO_8859_1},
1781 {"fr_FR" , C_ISO_8859_1 , C_ISO_8859_1},
1782 {"fr_LU" , C_ISO_8859_1 , C_ISO_8859_1},
1783 {"ga_IE" , C_ISO_8859_1 , C_ISO_8859_1},
1784 {"gl_ES" , C_ISO_8859_1 , C_ISO_8859_1},
1785 {"gv_GB" , C_ISO_8859_1 , C_ISO_8859_1},
1786 {"id_ID" , C_ISO_8859_1 , C_ISO_8859_1},
1787 {"is_IS" , C_ISO_8859_1 , C_ISO_8859_1},
1788 {"it_CH" , C_ISO_8859_1 , C_ISO_8859_1},
1789 {"it_IT" , C_ISO_8859_1 , C_ISO_8859_1},
1790 {"kl_GL" , C_ISO_8859_1 , C_ISO_8859_1},
1791 {"kw_GB" , C_ISO_8859_1 , C_ISO_8859_1},
1792 {"ms_MY" , C_ISO_8859_1 , C_ISO_8859_1},
1793 {"nl_BE" , C_ISO_8859_1 , C_ISO_8859_1},
1794 {"nl_NL" , C_ISO_8859_1 , C_ISO_8859_1},
1795 {"nn_NO" , C_ISO_8859_1 , C_ISO_8859_1},
1796 {"no_NO" , C_ISO_8859_1 , C_ISO_8859_1},
1797 {"oc_FR" , C_ISO_8859_1 , C_ISO_8859_1},
1798 {"pt_BR" , C_ISO_8859_1 , C_ISO_8859_1},
1799 {"pt_PT" , C_ISO_8859_1 , C_ISO_8859_1},
1800 {"sq_AL" , C_ISO_8859_1 , C_ISO_8859_1},
1801 {"sv_FI" , C_ISO_8859_1 , C_ISO_8859_1},
1802 {"sv_SE" , C_ISO_8859_1 , C_ISO_8859_1},
1803 {"tl_PH" , C_ISO_8859_1 , C_ISO_8859_1},
1804 {"uz_UZ" , C_ISO_8859_1 , C_ISO_8859_1},
1805 {"wa_BE" , C_ISO_8859_1 , C_ISO_8859_1},
1806
1807 {"bs_BA" , C_ISO_8859_2 , C_ISO_8859_2},
1808 {"cs_CZ" , C_ISO_8859_2 , C_ISO_8859_2},
1809 {"hr_HR" , C_ISO_8859_2 , C_ISO_8859_2},
1810 {"hu_HU" , C_ISO_8859_2 , C_ISO_8859_2},
1811 {"pl_PL" , C_ISO_8859_2 , C_ISO_8859_2},
1812 {"ro_RO" , C_ISO_8859_2 , C_ISO_8859_2},
1813 {"sk_SK" , C_ISO_8859_2 , C_ISO_8859_2},
1814 {"sl_SI" , C_ISO_8859_2 , C_ISO_8859_2},
1815
1816 {"sr_YU@cyrillic" , C_ISO_8859_5 , C_ISO_8859_5},
1817 {"sr_YU" , C_ISO_8859_2 , C_ISO_8859_2},
1818
1819 {"mt_MT" , C_ISO_8859_3 , C_ISO_8859_3},
1820
1821 {"lt_LT.iso88594" , C_ISO_8859_4 , C_ISO_8859_4},
1822 {"lt_LT.ISO8859-4" , C_ISO_8859_4 , C_ISO_8859_4},
1823 {"lt_LT.ISO_8859-4" , C_ISO_8859_4 , C_ISO_8859_4},
1824 {"lt_LT" , C_ISO_8859_13 , C_ISO_8859_13},
1825
1826 {"mk_MK" , C_ISO_8859_5 , C_ISO_8859_5},
1827
1828 {"ar_AE" , C_ISO_8859_6 , C_ISO_8859_6},
1829 {"ar_BH" , C_ISO_8859_6 , C_ISO_8859_6},
1830 {"ar_DZ" , C_ISO_8859_6 , C_ISO_8859_6},
1831 {"ar_EG" , C_ISO_8859_6 , C_ISO_8859_6},
1832 {"ar_IQ" , C_ISO_8859_6 , C_ISO_8859_6},
1833 {"ar_JO" , C_ISO_8859_6 , C_ISO_8859_6},
1834 {"ar_KW" , C_ISO_8859_6 , C_ISO_8859_6},
1835 {"ar_LB" , C_ISO_8859_6 , C_ISO_8859_6},
1836 {"ar_LY" , C_ISO_8859_6 , C_ISO_8859_6},
1837 {"ar_MA" , C_ISO_8859_6 , C_ISO_8859_6},
1838 {"ar_OM" , C_ISO_8859_6 , C_ISO_8859_6},
1839 {"ar_QA" , C_ISO_8859_6 , C_ISO_8859_6},
1840 {"ar_SA" , C_ISO_8859_6 , C_ISO_8859_6},
1841 {"ar_SD" , C_ISO_8859_6 , C_ISO_8859_6},
1842 {"ar_SY" , C_ISO_8859_6 , C_ISO_8859_6},
1843 {"ar_TN" , C_ISO_8859_6 , C_ISO_8859_6},
1844 {"ar_YE" , C_ISO_8859_6 , C_ISO_8859_6},
1845
1846 {"el_GR" , C_ISO_8859_7 , C_ISO_8859_7},
1847 {"he_IL" , C_ISO_8859_8 , C_ISO_8859_8},
1848 {"iw_IL" , C_ISO_8859_8 , C_ISO_8859_8},
1849 {"tr_TR" , C_ISO_8859_9 , C_ISO_8859_9},
1850
1851 {"lv_LV" , C_ISO_8859_13 , C_ISO_8859_13},
1852 {"mi_NZ" , C_ISO_8859_13 , C_ISO_8859_13},
1853
1854 {"cy_GB" , C_ISO_8859_14 , C_ISO_8859_14},
1855
1856 {"ar_IN" , C_UTF_8 , C_UTF_8},
1857 {"en_IN" , C_UTF_8 , C_UTF_8},
1858 {"se_NO" , C_UTF_8 , C_UTF_8},
1859 {"ta_IN" , C_UTF_8 , C_UTF_8},
1860 {"te_IN" , C_UTF_8 , C_UTF_8},
1861 {"ur_PK" , C_UTF_8 , C_UTF_8},
1862
1863 {"th_TH" , C_TIS_620 , C_TIS_620},
1864 /* {"th_TH" , C_WINDOWS_874}, */
1865 /* {"th_TH" , C_ISO_8859_11}, */
1866
1867 {"ka_GE" , C_GEORGIAN_PS , C_GEORGIAN_PS},
1868 {"vi_VN.TCVN" , C_TCVN5712_1 , C_TCVN5712_1},
1869
1870 {"C" , C_US_ASCII , C_US_ASCII},
1871 {"POSIX" , C_US_ASCII , C_US_ASCII},
1872 {"ANSI_X3.4-1968" , C_US_ASCII , C_US_ASCII},
1873};
1874
1875static GHashTable *conv_get_charset_to_str_table(void)
1876{
1877 static GHashTable *table;
1878 gint i;
1879 S_LOCK_DEFINE_STATIC(table);
1880
1881 S_LOCK(table);
1882
1883 if (table) {
1884 S_UNLOCK(table);
1885 return table;
1886 }
1887
1888 table = g_hash_table_new(NULL, g_direct_equal);
1889
1890 for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
1891 if (g_hash_table_lookup(table, GUINT_TO_POINTER(charsets[i].charset))
1892 == NULL) {
1893 g_hash_table_insert
1894 (table, GUINT_TO_POINTER(charsets[i].charset),
1895 charsets[i].name);
1896 }
1897 }
1898
1899 S_UNLOCK(table);
1900 return table;
1901}
1902
1903static GHashTable *conv_get_charset_from_str_table(void)
1904{
1905 static GHashTable *table;
1906 S_LOCK_DEFINE_STATIC(table);
1907
1908 gint i;
1909
1910 S_LOCK(table);
1911
1912 if (table) {
1913 S_UNLOCK(table);
1914 return table;
1915 }
1916
1917 table = g_hash_table_new(str_case_hash, str_case_equal);
1918
1919 for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
1920 g_hash_table_insert(table, charsets[i].name,
1921 GUINT_TO_POINTER(charsets[i].charset));
1922 }
1923
1924 S_UNLOCK(table);
1925 return table;
1926}
1927
1928const gchar *conv_get_charset_str(CharSet charset)
1929{
1930 GHashTable *table;
1931
1932 table = conv_get_charset_to_str_table();
1933 return g_hash_table_lookup(table, GUINT_TO_POINTER(charset));
1934}
1935
1936CharSet conv_get_charset_from_str(const gchar *charset)
1937{
1938 GHashTable *table;
1939
1940 if (!charset) return C_AUTO;
1941
1942 table = conv_get_charset_from_str_table();
1943 return GPOINTER_TO_UINT(g_hash_table_lookup(table, charset));
1944}
1945
1946CharSet conv_get_locale_charset(void)
1947{
1948 static CharSet cur_charset = -1;
1949 const gchar *cur_locale;
1950 const gchar *p;
1951#if !defined(G_OS_WIN32) && !defined(__APPLE__)
1952 gint i;
1953#endif
1954 S_LOCK_DEFINE_STATIC(cur_charset);
1955
1956 S_LOCK(cur_charset);
1957
1958 if (cur_charset != -1) {
1959 S_UNLOCK(cur_charset);
1960 return cur_charset;
1961 }
1962
1963 cur_locale = conv_get_current_locale();
1964 if (!cur_locale) {
1965 cur_charset = C_US_ASCII;
1966 S_UNLOCK(cur_charset);
1967 return cur_charset;
1968 }
1969
1970 if (strcasestr(cur_locale, "UTF-8") || strcasestr(cur_locale, "utf8")) {
1971 cur_charset = C_UTF_8;
1972 S_UNLOCK(cur_charset);
1973 return cur_charset;
1974 }
1975
1976 if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') {
1977 cur_charset = C_ISO_8859_15;
1978 S_UNLOCK(cur_charset);
1979 return cur_charset;
1980 }
1981
1982#if defined(G_OS_WIN32) || defined(__APPLE__)
1983 cur_charset = conv_get_charset_from_str(conv_get_locale_charset_str());
1984
1985 S_UNLOCK(cur_charset);
1986 return cur_charset;
1987#else
1988 for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) {
1989 const gchar *p;
1990
1991 /* "ja_JP.EUC" matches with "ja_JP.eucJP", "ja_JP.EUC" and
1992 "ja_JP". "ja_JP" matches with "ja_JP.xxxx" and "ja" */
1993 if (!g_ascii_strncasecmp(cur_locale, locale_table[i].locale,
1994 strlen(locale_table[i].locale))) {
1995 cur_charset = locale_table[i].charset;
1996 S_UNLOCK(cur_charset);
1997 return cur_charset;
1998 } else if ((p = strchr(locale_table[i].locale, '_')) &&
1999 !strchr(p + 1, '.')) {
2000 if (strlen(cur_locale) == 2 &&
2001 !g_ascii_strncasecmp(cur_locale,
2002 locale_table[i].locale, 2)) {
2003 cur_charset = locale_table[i].charset;
2004 S_UNLOCK(cur_charset);
2005 return cur_charset;
2006 }
2007 }
2008 }
2009
2010 cur_charset = C_AUTO;
2011 S_UNLOCK(cur_charset);
2012 return cur_charset;
2013#endif
2014}
2015
2016const gchar *conv_get_locale_charset_str(void)
2017{
2018 static const gchar *codeset = NULL;
2019 S_LOCK_DEFINE_STATIC(codeset);
2020
2021 S_LOCK(codeset);
2022
2023 if (!codeset) {
2024#if defined(G_OS_WIN32) || defined(__APPLE__)
2025 g_get_charset(&codeset);
2026 if (!strcmp(codeset, CS_US_ASCII) ||
2027 !strcmp(codeset, CS_ANSI_X3_4_1968))
2028 codeset = CS_INTERNAL;
2029#else
2030 codeset = conv_get_charset_str(conv_get_locale_charset());
2031#endif
2032 }
2033
2034 if (codeset) {
2035 S_UNLOCK(codeset);
2036 return codeset;
2037 }
2038
2039 S_UNLOCK(codeset);
2040 return CS_INTERNAL;
2041}
2042
2043CharSet conv_get_internal_charset(void)
2044{
2045 return C_INTERNAL;
2046}
2047
2048const gchar *conv_get_internal_charset_str(void)
2049{
2050 return CS_INTERNAL;
2051}
2052
2053CharSet conv_get_outgoing_charset(void)
2054{
2055 static CharSet out_charset = -1;
2056 const gchar *cur_locale;
2057 const gchar *p;
2058 gint i;
2059 S_LOCK_DEFINE_STATIC(out_charset);
2060
2061 S_LOCK(out_charset);
2062
2063 if (out_charset != -1) {
2064 S_UNLOCK(out_charset);
2065 return out_charset;
2066 }
2067
2068 cur_locale = conv_get_current_locale();
2069 if (!cur_locale) {
2070 out_charset = C_AUTO;
2071 S_UNLOCK(out_charset);
2072 return out_charset;
2073 }
2074
2075 if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') {
2076 out_charset = C_ISO_8859_15;
2077 S_UNLOCK(out_charset);
2078 return out_charset;
2079 }
2080
2081 for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) {
2082 const gchar *p;
2083
2084 if (!g_ascii_strncasecmp(cur_locale, locale_table[i].locale,
2085 strlen(locale_table[i].locale))) {
2086 out_charset = locale_table[i].out_charset;
2087 break;
2088 } else if ((p = strchr(locale_table[i].locale, '_')) &&
2089 !strchr(p + 1, '.')) {
2090 if (strlen(cur_locale) == 2 &&
2091 !g_ascii_strncasecmp(cur_locale,
2092 locale_table[i].locale, 2)) {
2093 out_charset = locale_table[i].out_charset;
2094 break;
2095 }
2096 }
2097 }
2098
2099 S_UNLOCK(out_charset);
2100 return out_charset;
2101}
2102
2103const gchar *conv_get_outgoing_charset_str(void)
2104{
2105 CharSet out_charset;
2106 const gchar *str;
2107
2108 out_charset = conv_get_outgoing_charset();
2109 str = conv_get_charset_str(out_charset);
2110
2111 return str ? str : CS_UTF_8;
2112}
2113
2114gboolean conv_is_multibyte_encoding(CharSet encoding)
2115{
2116 switch (encoding) {
2117 case C_EUC_JP:
2118 case C_EUC_JP_MS:
2119 case C_EUC_KR:
2120 case C_EUC_TW:
2121 case C_EUC_CN:
2122 case C_ISO_2022_JP:
2123 case C_ISO_2022_JP_2:
2124 case C_ISO_2022_JP_3:
2125 case C_ISO_2022_KR:
2126 case C_ISO_2022_CN:
2127 case C_SHIFT_JIS:
2128 case C_CP932:
2129 case C_GB2312:
2130 case C_GBK:
2131 case C_BIG5:
2132 case C_UTF_8:
2133 case C_UTF_7:
2134 return TRUE;
2135 default:
2136 return FALSE;
2137 }
2138}
2139
2140const gchar *conv_get_current_locale(void)
2141{
2142 static const gchar *cur_locale;
2143 S_LOCK_DEFINE_STATIC(cur_locale);
2144
2145 S_LOCK(cur_locale);
2146
2147 if (!cur_locale) {
2148#ifdef G_OS_WIN32
2149 cur_locale = g_win32_getlocale();
2150#else
2151 cur_locale = g_getenv("LC_ALL");
2152 if (!cur_locale || *cur_locale == '\0')
2153 cur_locale = g_getenv("LC_CTYPE");
2154 if (!cur_locale || *cur_locale == '\0')
2155 cur_locale = g_getenv("LANG");
2156#ifdef HAVE_LOCALE_H
2157 if (!cur_locale || *cur_locale == '\0')
2158 cur_locale = setlocale(LC_CTYPE, NULL);
2159#endif /* HAVE_LOCALE_H */
2160#endif /* G_OS_WIN32 */
2161
2162 debug_print("current locale: %s\n",
2163 cur_locale ? cur_locale : "(none)");
2164 }
2165
2166 S_UNLOCK(cur_locale);
2167 return cur_locale;
2168}
2169
2170gboolean conv_is_ja_locale(void)
2171{
2172 static gint is_ja_locale = -1;
2173 const gchar *cur_locale;
2174 S_LOCK_DEFINE_STATIC(is_ja_locale);
2175
2176 S_LOCK(is_ja_locale);
2177
2178 if (is_ja_locale != -1) {
2179 S_UNLOCK(is_ja_locale);
2180 return is_ja_locale != 0;
2181 }
2182
2183 is_ja_locale = 0;
2184 cur_locale = conv_get_current_locale();
2185 if (cur_locale) {
2186 if (g_ascii_strncasecmp(cur_locale, "ja", 2) == 0)
2187 is_ja_locale = 1;
2188 }
2189
2190 S_UNLOCK(is_ja_locale);
2191 return is_ja_locale != 0;
2192}
2193
2194void conv_set_autodetect_type(ConvADType type)
2195{
2196 conv_ad_type = type;
2197}
2198
2199ConvADType conv_get_autodetect_type(void)
2200{
2201 return conv_ad_type;
2202}
2203
2204gchar *conv_unmime_header(const gchar *str, const gchar *default_encoding)
2205{
2206 gchar *buf;
2207 gchar *decoded_str;
2208
2209 if (is_ascii_str(str))
2210 return unmime_header(str);
2211
2212 if (default_encoding) {
2213 buf = conv_codeset_strdup
2214 (str, default_encoding, CS_INTERNAL);
2215 if (buf) {
2216 decoded_str = unmime_header(buf);
2217 g_free(buf);
2218 return decoded_str;
2219 }
2220 }
2221
2222 if (conv_ad_type == C_AD_JAPANESE ||
2223 (conv_ad_type == C_AD_BY_LOCALE && conv_is_ja_locale()))
2224 buf = conv_anytodisp(str, NULL);
2225 else
2226 buf = conv_localetodisp(str, NULL);
2227
2228 decoded_str = unmime_header(buf);
2229 g_free(buf);
2230
2231 return decoded_str;
2232}
2233
2234#define MAX_LINELEN 76
2235#define MAX_HARD_LINELEN 996
2236#define MIMESEP_BEGIN "=?"
2237#define MIMESEP_END "?="
2238
2239#define B64LEN(len) ((len) / 3 * 4 + ((len) % 3 ? 4 : 0))
2240
2241#define LBREAK_IF_REQUIRED(cond, is_plain_text) \
2242{ \
2243 if (len - (destp - dest) < MAX_LINELEN + 2) { \
2244 *destp = '\0'; \
2245 return; \
2246 } \
2247 \
2248 if ((cond) && *srcp) { \
2249 if (destp > dest && left < MAX_LINELEN - 1) { \
2250 if (g_ascii_isspace(*(destp - 1))) \
2251 destp--; \
2252 else if (is_plain_text && \
2253 g_ascii_isspace(*srcp)) \
2254 srcp++; \
2255 if (*srcp) { \
2256 *destp++ = '\n'; \
2257 *destp++ = ' '; \
2258 left = MAX_LINELEN - 1; \
2259 } \
2260 } \
2261 } \
2262}
2263
2264void conv_encode_header(gchar *dest, gint len, const gchar *src,
2265 gint header_len, gboolean addr_field,
2266 const gchar *out_encoding)
2267{
2268 const gchar *src_encoding;
2269 gint mimestr_len;
2270 gchar *mimesep_enc;
2271 gint left;
2272 const gchar *srcp = src;
2273 gchar *destp = dest;
2274 gboolean use_base64;
2275
2276 g_return_if_fail(g_utf8_validate(src, -1, NULL) == TRUE);
2277
2278 src_encoding = CS_INTERNAL;
2279 if (!out_encoding)
2280 out_encoding = conv_get_outgoing_charset_str();
2281 if (!strcmp(out_encoding, CS_US_ASCII))
2282 out_encoding = CS_ISO_8859_1;
2283
2284 if (!g_ascii_strncasecmp(out_encoding, "ISO-8859-", 9) ||
2285 !g_ascii_strncasecmp(out_encoding, "KOI8-", 5) ||
2286 !g_ascii_strncasecmp(out_encoding, "Windows-", 8)) {
2287 use_base64 = FALSE;
2288 mimesep_enc = "?Q?";
2289 } else {
2290 use_base64 = TRUE;
2291 mimesep_enc = "?B?";
2292 }
2293
2294 mimestr_len = strlen(MIMESEP_BEGIN) + strlen(mimesep_enc) +
2295 strlen(MIMESEP_END);
2296
2297 left = MAX_LINELEN - header_len;
2298
2299 while (*srcp) {
2300 gboolean in_quote = FALSE;
2301
2302 LBREAK_IF_REQUIRED(left <= 0, TRUE);
2303
2304 while (g_ascii_isspace(*srcp)) {
2305 *destp++ = *srcp++;
2306 left--;
2307 LBREAK_IF_REQUIRED(left <= 0, TRUE);
2308 }
2309
2310 /* output as it is if the next word is ASCII string */
2311 if (!is_next_nonascii(srcp)) {
2312 gint word_len;
2313
2314 word_len = get_next_word_len(srcp);
2315 LBREAK_IF_REQUIRED(left < word_len, TRUE);
2316 while (word_len > 0) {
2317 LBREAK_IF_REQUIRED(left + (MAX_HARD_LINELEN - MAX_LINELEN) <= 0, TRUE)
2318 *destp++ = *srcp++;
2319 left--;
2320 word_len--;
2321 }
2322
2323 continue;
2324 }
2325
2326 /* don't include parentheses in encoded strings */
2327 if (addr_field && (*srcp == '(' || *srcp == ')')) {
2328 LBREAK_IF_REQUIRED(left < 2, FALSE);
2329 *destp++ = *srcp++;
2330 left--;
2331 }
2332
2333 while (1) {
2334 gint mb_len = 0;
2335 gint cur_len = 0;
2336 gchar *part_str;
2337 gchar *out_str;
2338 gchar *enc_str;
2339 const gchar *p = srcp;
2340 const gchar *block_encoding = out_encoding;
2341 gint out_str_len;
2342 gint out_enc_str_len;
2343 gint mime_block_len;
2344 gint error = 0;
2345 gboolean cont = FALSE;
2346
2347 while (*p != '\0') {
2348 if (*p == '"')
2349 in_quote ^= TRUE;
2350 else if (!in_quote) {
2351 if (g_ascii_isspace(*p) &&
2352 !is_next_nonascii(p + 1))
2353 break;
2354 /* don't include parentheses in encoded
2355 strings */
2356 if (addr_field &&
2357 (*p == '(' || *p == ')'))
2358 break;
2359 }
2360
2361 mb_len = g_utf8_skip[*(guchar *)p];
2362
2363 part_str = g_strndup(srcp, cur_len + mb_len);
2364 out_str = conv_codeset_strdup_full
2365 (part_str, src_encoding, block_encoding,
2366 &error);
2367 if (!out_str || error != 0) {
2368 g_warning("conv_encode_header(): code conversion failed. Keeping UTF-8.\n");
2369 out_str = g_strdup(part_str);
2370 block_encoding = CS_UTF_8;
2371 }
2372 out_str_len = strlen(out_str);
2373
2374 if (use_base64)
2375 out_enc_str_len = B64LEN(out_str_len);
2376 else
2377 out_enc_str_len =
2378 qp_get_q_encoding_len
2379 ((guchar *)out_str);
2380
2381 g_free(out_str);
2382 g_free(part_str);
2383
2384 if (mimestr_len + strlen(block_encoding) + out_enc_str_len <= left) {
2385 cur_len += mb_len;
2386 p += mb_len;
2387 } else if (cur_len == 0) {
2388 LBREAK_IF_REQUIRED(1, FALSE);
2389 if (*p == '"')
2390 in_quote ^= TRUE;
2391 continue;
2392 } else {
2393 cont = TRUE;
2394 if (*p == '"')
2395 in_quote ^= TRUE;
2396 break;
2397 }
2398 }
2399
2400 if (cur_len > 0) {
2401 error = 0;
2402 part_str = g_strndup(srcp, cur_len);
2403 out_str = conv_codeset_strdup_full
2404 (part_str, src_encoding, block_encoding,
2405 &error);
2406 if (!out_str || error != 0) {
2407 g_warning("conv_encode_header(): code conversion failed\n");
2408 out_str = g_strdup(part_str);
2409 block_encoding = CS_UTF_8;
2410 }
2411 out_str_len = strlen(out_str);
2412
2413 if (use_base64)
2414 out_enc_str_len = B64LEN(out_str_len);
2415 else
2416 out_enc_str_len =
2417 qp_get_q_encoding_len
2418 ((guchar *)out_str);
2419
2420 enc_str = g_malloc(out_enc_str_len + 1);
2421 if (use_base64)
2422 base64_encode(enc_str,
2423 (guchar *)out_str,
2424 out_str_len);
2425 else
2426 qp_q_encode(enc_str, (guchar *)out_str);
2427
2428 /* output MIME-encoded string block */
2429 mime_block_len = mimestr_len +
2430 strlen(block_encoding) +
2431 strlen(enc_str);
2432 g_snprintf(destp, mime_block_len + 1,
2433 MIMESEP_BEGIN "%s%s%s" MIMESEP_END,
2434 block_encoding, mimesep_enc,
2435 enc_str);
2436 destp += mime_block_len;
2437 srcp += cur_len;
2438
2439 left -= mime_block_len;
2440
2441 g_free(enc_str);
2442 g_free(out_str);
2443 g_free(part_str);
2444 }
2445
2446 LBREAK_IF_REQUIRED(cont, FALSE);
2447
2448 if (cur_len == 0)
2449 break;
2450 }
2451 }
2452
2453 *destp = '\0';
2454}
2455
2456#undef LBREAK_IF_REQUIRED
2457
2458#define INT_TO_HEX_UPPER(outp, val) \
2459{ \
2460 if ((val) < 10) \
2461 *outp = '0' + (val); \
2462 else \
2463 *outp = 'A' + (val) - 10; \
2464}
2465
2466#define IS_ESCAPE_CHAR(c) \
2467 (c < 0x20 || c > 0x7f || \
2468 strchr("\t \r\n*'%!#$&~`,{}|()<>@,;:\\\"/[]?=", c))
2469
2470static gchar *encode_rfc2231_filename(const gchar *str)
2471{
2472 const gchar *p;
2473 gchar *out;
2474 gchar *outp;
2475
2476 outp = out = g_malloc(strlen(str) * 3 + 1);
2477
2478 for (p = str; *p != '\0'; ++p) {
2479 guchar ch = *(guchar *)p;
2480
2481 if (IS_ESCAPE_CHAR(ch)) {
2482 *outp++ = '%';
2483 INT_TO_HEX_UPPER(outp, ch >> 4);
2484 ++outp;
2485 INT_TO_HEX_UPPER(outp, ch & 0x0f);
2486 ++outp;
2487 } else
2488 *outp++ = ch;
2489 }
2490
2491 *outp = '\0';
2492 return out;
2493}
2494
2495gchar *conv_encode_filename(const gchar *src, const gchar *param_name,
2496 const gchar *out_encoding)
2497{
2498 gint name_len, max_linelen;
2499 gchar *out_str, *enc_str;
2500 gchar cur_param[80];
2501 GString *string;
2502 gint count = 0;
2503 gint cur_left_len;
2504 gchar *p;
2505
2506 g_return_val_if_fail(src != NULL, NULL);
2507 g_return_val_if_fail(param_name != NULL, NULL);
2508
2509 if (is_ascii_str(src))
2510 return g_strdup_printf(" %s=\"%s\"", param_name, src);
2511
2512 name_len = strlen(param_name);
2513 max_linelen = MAX_LINELEN - name_len - 3;
2514
2515 if (!out_encoding)
2516 out_encoding = conv_get_outgoing_charset_str();
2517 if (!strcmp(out_encoding, CS_US_ASCII))
2518 out_encoding = CS_ISO_8859_1;
2519
2520 out_str = conv_codeset_strdup(src, CS_INTERNAL, out_encoding);
2521 if (!out_str)
2522 return NULL;
2523 enc_str = encode_rfc2231_filename(out_str);
2524 g_free(out_str);
2525
2526 if (strlen(enc_str) <= max_linelen) {
2527 gchar *ret;
2528 ret = g_strdup_printf(" %s*=%s''%s",
2529 param_name, out_encoding, enc_str);
2530 g_free(enc_str);
2531 return ret;
2532 }
2533
2534 string = g_string_new(NULL);
2535 g_string_printf(string, " %s*0*=%s''", param_name, out_encoding);
2536 cur_left_len = MAX_LINELEN - string->len;
2537
2538 p = enc_str;
2539
2540 while (*p != '\0') {
2541 if ((*p == '%' && cur_left_len < 4) ||
2542 (*p != '%' && cur_left_len < 2)) {
2543 gint len;
2544
2545 g_string_append(string, ";\n");
2546 ++count;
2547 len = g_snprintf(cur_param, sizeof(cur_param),
2548 " %s*%d*=", param_name, count);
2549 g_string_append(string, cur_param);
2550 cur_left_len = MAX_LINELEN - len;
2551 }
2552
2553 if (*p == '%') {
2554 g_string_append_len(string, p, 3);
2555 p += 3;
2556 cur_left_len -= 3;
2557 } else {
2558 g_string_append_c(string, *p);
2559 ++p;
2560 --cur_left_len;
2561 }
2562 }
2563
2564 g_free(enc_str);
2565
2566 return g_string_free(string, FALSE);
2567}
2568
2569static gint conv_copy_file_with_gconvert(const gchar *src, const gchar *dest,
2570 const gchar *encoding)
2571{
2572 gchar *src_s = NULL;
2573 gsize len = 0, dlen = 0;
2574 gchar *dest_s;
2575 GError *error = NULL;
2576
2577 g_return_val_if_fail(src != NULL, -1);
2578 g_return_val_if_fail(dest != NULL, -1);
2579 g_return_val_if_fail(encoding != NULL, -1);
2580
2581 if (g_file_get_contents(src, &src_s, &len, &error) == FALSE) {
2582 g_warning("conv_copy_utf16_file(): %s: %s", src, error->message);
2583 g_error_free(error);
2584 return -1;
2585 }
2586
2587 dest_s = g_convert(src_s, len, CS_UTF_8, encoding, NULL, &dlen, &error);
2588 if (!dest_s) {
2589 g_warning("conv_copy_utf16_file(): %s: %s", src, error->message);
2590 g_error_free(error);
2591 g_free(src_s);
2592 return -1;
2593 }
2594
2595 if (g_file_set_contents(dest, dest_s, dlen, &error) == FALSE) {
2596 g_warning("conv_copy_utf16_file(): %s: %s", dest, error->message);
2597 g_error_free(error);
2598 g_free(dest_s);
2599 g_free(src_s);
2600 return -1;
2601 }
2602
2603 g_free(dest_s);
2604 g_free(src_s);
2605 return 0;
2606}
2607
2608gint conv_copy_file(const gchar *src, const gchar *dest, const gchar *encoding)
2609{
2610 FILE *src_fp, *dest_fp;
2611 gchar buf[BUFFSIZE];
2612 CodeConverter *conv;
2613 gboolean err = FALSE;
2614 CharSet charset;
2615
2616 charset = conv_get_charset_from_str(encoding);
2617 if (charset == C_UTF_16 || charset == C_UTF_16BE || charset == C_UTF_16LE) {
2618 return conv_copy_file_with_gconvert(src, dest, encoding);
2619 }
2620
2621 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2622 FILE_OP_ERROR(src, "fopen");
2623 return -1;
2624 }
2625 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2626 FILE_OP_ERROR(dest, "fopen");
2627 fclose(src_fp);
2628 return -1;
2629 }
2630
2631 if (change_file_mode_rw(dest_fp, dest) < 0) {
2632 FILE_OP_ERROR(dest, "chmod");
2633 g_warning("can't change file mode\n");
2634 }
2635
2636 conv = conv_code_converter_new(encoding, NULL);
2637
2638 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2639 gchar *outbuf;
2640
2641 outbuf = conv_convert(conv, buf);
2642 if (outbuf) {
2643 fputs(outbuf, dest_fp);
2644 g_free(outbuf);
2645 } else
2646 fputs(buf, dest_fp);
2647 }
2648
2649 conv_code_converter_destroy(conv);
2650
2651 if (ferror(src_fp)) {
2652 FILE_OP_ERROR(src, "fgets");
2653 err = TRUE;
2654 }
2655 fclose(src_fp);
2656 if (fclose(dest_fp) == EOF) {
2657 FILE_OP_ERROR(dest, "fclose");
2658 err = TRUE;
2659 }
2660 if (err) {
2661 g_unlink(dest);
2662 return -1;
2663 }
2664
2665 return 0;
2666}
2667
2668gint conv_copy_dir(const gchar *src, const gchar *dest, const gchar *encoding)
2669{
2670 GDir *dir;
2671 const gchar *dir_name;
2672 gchar *src_file;
2673 gchar *dest_file;
2674
2675 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
2676 g_warning("failed to open directory: %s\n", src);
2677 return -1;
2678 }
2679
2680 if (make_dir_hier(dest) < 0) {
2681 g_dir_close(dir);
2682 return -1;
2683 }
2684
2685 while ((dir_name = g_dir_read_name(dir)) != NULL) {
2686 src_file = g_strconcat(src, G_DIR_SEPARATOR_S, dir_name, NULL);
2687 dest_file = g_strconcat(dest, G_DIR_SEPARATOR_S, dir_name,
2688 NULL);
2689 if (is_file_exist(src_file))
2690 conv_copy_file(src_file, dest_file, encoding);
2691 g_free(dest_file);
2692 g_free(src_file);
2693 }
2694
2695 g_dir_close(dir);
2696
2697 return 0;
2698}
2699
2700CharSet conv_check_file_encoding(const gchar *file)
2701{
2702 FILE *fp;
2703 gchar buf[BUFFSIZE];
2704 CharSet enc;
2705 const gchar *enc_str;
2706 gboolean is_locale = TRUE, is_utf8 = TRUE;
2707 size_t size;
2708
2709 g_return_val_if_fail(file != NULL, C_AUTO);
2710
2711 enc = conv_get_locale_charset();
2712 enc_str = conv_get_locale_charset_str();
2713 if (enc == C_UTF_8)
2714 is_locale = FALSE;
2715
2716 if ((fp = g_fopen(file, "rb")) == NULL) {
2717 FILE_OP_ERROR(file, "fopen");
2718 return C_AUTO;
2719 }
2720
2721 /* UTF-16 check */
2722 if ((size = fread(buf, 2, BUFFSIZE / 2, fp)) > 0) {
2723 CharSet guess_enc = C_AUTO;
2724
2725 debug_print("conv_check_file_encoding: check first %d bytes of file %s\n", size * 2, file);
2726
2727 /* BOM check */
2728 if ((buf[0] & 0xff) == 0xfe && (buf[1] & 0xff) == 0xff) {
2729 debug_print("conv_check_file_encoding: UTF-16 BOM (BE) found\n");
2730 guess_enc = C_UTF_16; /* UTF-16BE */
2731 } else if ((buf[0] & 0xff) == 0xff && (buf[1] & 0xff) == 0xfe) {
2732 debug_print("conv_check_file_encoding: UTF-16 BOM (LE) found\n");
2733 guess_enc = C_UTF_16; /* UTF-16LE */
2734 }
2735 if (guess_enc != C_AUTO) {
2736 fclose(fp);
2737 return guess_enc;
2738 }
2739
2740 /* search UTF-16 CR/LF */
2741 if (memchr(buf, 0x00, size * 2) != NULL) {
2742 gint i;
2743 guchar c1, c2;
2744
2745 for (i = 0; i < size; i++) {
2746 c1 = buf[i * 2] & 0xff;
2747 c2 = buf[i * 2 + 1] & 0xff;
2748 if (c1 == 0x00 && c2 == 0x0d) { /* UTF-16BE CR */
2749 i++;
2750 if (i >= size) {
2751 break;
2752 }
2753 c1 = buf[i * 2] & 0xff;
2754 c2 = buf[i * 2 + 1] & 0xff;
2755 if (c1 == 0x00 && c2 == 0x0a) { /* UTF-16BE LF */
2756 guess_enc = C_UTF_16BE;
2757 break;
2758 }
2759 } else if (c1 == 0x0d && c2 == 0x00) { /* UTF-16LE CR */
2760 i++;
2761 if (i >= size) {
2762 break;
2763 }
2764 c1 = buf[i * 2] & 0xff;
2765 c2 = buf[i * 2 + 1] & 0xff;
2766 if (c1 == 0x0a && c2 == 0x00) { /* UTF-16LE LF */
2767 guess_enc = C_UTF_16LE;
2768 break;
2769 }
2770 } else if (c1 == 0x00 && c2 == 0x0a) { /* UTF-16BE LF */
2771 guess_enc = C_UTF_16BE;
2772 break;
2773 } else if (c1 == 0x0a && c2 == 0x00) { /* UTF-16LE LF */
2774 guess_enc = C_UTF_16LE;
2775 break;
2776 }
2777 }
2778
2779 if (guess_enc != C_AUTO) {
2780 debug_print("conv_check_file_encoding: %s detected\n",
2781 conv_get_charset_str(guess_enc));
2782 fclose(fp);
2783 return guess_enc;
2784 }
2785 }
2786 }
2787
2788 rewind(fp);
2789
2790 while (fgets(buf, sizeof(buf), fp) != NULL) {
2791 gchar *str;
2792 gint error = 0;
2793
2794 if (is_locale) {
2795 str = conv_codeset_strdup_full(buf, enc_str,
2796 CS_INTERNAL, &error);
2797 if (!str || error != 0)
2798 is_locale = FALSE;
2799 g_free(str);
2800 }
2801
2802 if (is_utf8 && g_utf8_validate(buf, -1, NULL) == FALSE) {
2803 is_utf8 = FALSE;
2804 }
2805
2806 if (!is_locale && !is_utf8)
2807 break;
2808 }
2809
2810 fclose(fp);
2811
2812 if (is_locale)
2813 return enc;
2814 else if (is_utf8)
2815 return C_UTF_8;
2816 else
2817 return C_AUTO;
2818}
2819
2820gchar *conv_filename_from_utf8(const gchar *utf8_file)
2821{
2822 gchar *fs_file;
2823 GError *error = NULL;
2824
2825 g_return_val_if_fail(utf8_file != NULL, NULL);
2826
2827 fs_file = g_filename_from_utf8(utf8_file, -1, NULL, NULL, &error);
2828 if (error) {
2829 g_warning("failed to convert encoding of file name: %s\n",
2830 error->message);
2831 g_error_free(error);
2832 }
2833 if (!fs_file)
2834 fs_file = g_strdup(utf8_file);
2835
2836 return fs_file;
2837}
2838
2839gchar *conv_filename_to_utf8(const gchar *fs_file)
2840{
2841 gchar *utf8_file;
2842 GError *error = NULL;
2843
2844 g_return_val_if_fail(fs_file != NULL, NULL);
2845
2846 utf8_file = g_filename_to_utf8(fs_file, -1, NULL, NULL, &error);
2847 if (error) {
2848 g_warning("failed to convert encoding of file name: %s\n",
2849 error->message);
2850 g_error_free(error);
2851 }
2852 if (!utf8_file)
2853 utf8_file = g_strdup(fs_file);
2854
2855 return utf8_file;
2856}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2017 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __CODECONV_H__
21#define __CODECONV_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28#include <iconv.h>
29
30typedef struct _CodeConverter CodeConverter;
31
32typedef enum
33{
34 C_AUTO,
35 C_US_ASCII,
36 C_UTF_8,
37 C_UTF_7,
38 C_ISO_8859_1,
39 C_ISO_8859_2,
40 C_ISO_8859_3,
41 C_ISO_8859_4,
42 C_ISO_8859_5,
43 C_ISO_8859_6,
44 C_ISO_8859_7,
45 C_ISO_8859_8,
46 C_ISO_8859_9,
47 C_ISO_8859_10,
48 C_ISO_8859_11,
49 C_ISO_8859_13,
50 C_ISO_8859_14,
51 C_ISO_8859_15,
52 C_BALTIC,
53 C_CP932,
54 C_CP1250,
55 C_CP1251,
56 C_CP1252,
57 C_CP1253,
58 C_CP1254,
59 C_CP1255,
60 C_CP1256,
61 C_CP1257,
62 C_CP1258,
63 C_WINDOWS_932,
64 C_WINDOWS_1250,
65 C_WINDOWS_1251,
66 C_WINDOWS_1252,
67 C_WINDOWS_1253,
68 C_WINDOWS_1254,
69 C_WINDOWS_1255,
70 C_WINDOWS_1256,
71 C_WINDOWS_1257,
72 C_WINDOWS_1258,
73 C_KOI8_R,
74 C_KOI8_T,
75 C_KOI8_U,
76 C_ISO_2022_JP,
77 C_ISO_2022_JP_2,
78 C_ISO_2022_JP_3,
79 C_EUC_JP,
80 C_EUC_JP_MS,
81 C_SHIFT_JIS,
82 C_ISO_2022_KR,
83 C_EUC_KR,
84 C_ISO_2022_CN,
85 C_EUC_CN,
86 C_GB2312,
87 C_GBK,
88 C_EUC_TW,
89 C_BIG5,
90 C_BIG5_HKSCS,
91 C_TIS_620,
92 C_WINDOWS_874,
93 C_GEORGIAN_PS,
94 C_TCVN5712_1,
95 C_ISO_8859_16,
96 C_UTF_16,
97 C_UTF_16BE,
98 C_UTF_16LE
99} CharSet;
100
101typedef enum
102{
103 C_AD_BY_LOCALE,
104 C_AD_NEVER,
105 C_AD_JAPANESE
106} ConvADType;
107
108typedef gchar *(*CodeConvFunc) (const gchar *inbuf, gint *error);
109
110struct _CodeConverter
111{
112 CodeConvFunc code_conv_func;
113 gchar *src_encoding;
114 gchar *dest_encoding;
115};
116
117#define CS_AUTO "AUTO"
118#define CS_US_ASCII "US-ASCII"
119#define CS_ANSI_X3_4_1968 "ANSI_X3.4-1968"
120#define CS_UTF_8 "UTF-8"
121#define CS_UTF_7 "UTF-7"
122#define CS_ISO_8859_1 "ISO-8859-1"
123#define CS_ISO_8859_2 "ISO-8859-2"
124#define CS_ISO_8859_3 "ISO-8859-3"
125#define CS_ISO_8859_4 "ISO-8859-4"
126#define CS_ISO_8859_5 "ISO-8859-5"
127#define CS_ISO_8859_6 "ISO-8859-6"
128#define CS_ISO_8859_7 "ISO-8859-7"
129#define CS_ISO_8859_8 "ISO-8859-8"
130#define CS_ISO_8859_9 "ISO-8859-9"
131#define CS_ISO_8859_10 "ISO-8859-10"
132#define CS_ISO_8859_11 "ISO-8859-11"
133#define CS_ISO_8859_13 "ISO-8859-13"
134#define CS_ISO_8859_14 "ISO-8859-14"
135#define CS_ISO_8859_15 "ISO-8859-15"
136#define CS_BALTIC "BALTIC"
137#define CS_CP932 "CP932"
138#define CS_CP1250 "CP1250"
139#define CS_CP1251 "CP1251"
140#define CS_CP1252 "CP1252"
141#define CS_CP1253 "CP1253"
142#define CS_CP1254 "CP1254"
143#define CS_CP1255 "CP1255"
144#define CS_CP1256 "CP1256"
145#define CS_CP1257 "CP1257"
146#define CS_CP1258 "CP1258"
147#define CS_WINDOWS_932 "Windows-932"
148#define CS_WINDOWS_1250 "Windows-1250"
149#define CS_WINDOWS_1251 "Windows-1251"
150#define CS_WINDOWS_1252 "Windows-1252"
151#define CS_WINDOWS_1253 "Windows-1253"
152#define CS_WINDOWS_1254 "Windows-1254"
153#define CS_WINDOWS_1255 "Windows-1255"
154#define CS_WINDOWS_1256 "Windows-1256"
155#define CS_WINDOWS_1257 "Windows-1257"
156#define CS_WINDOWS_1258 "Windows-1258"
157#define CS_KOI8_R "KOI8-R"
158#define CS_KOI8_T "KOI8-T"
159#define CS_KOI8_U "KOI8-U"
160#define CS_ISO_2022_JP "ISO-2022-JP"
161#define CS_ISO_2022_JP_2 "ISO-2022-JP-2"
162#define CS_ISO_2022_JP_3 "ISO-2022-JP-3"
163#define CS_EUC_JP "EUC-JP"
164#define CS_EUCJP "EUCJP"
165#define CS_EUC_JP_MS "EUC-JP-MS"
166#define CS_SHIFT_JIS "Shift_JIS"
167#define CS_SHIFT__JIS "SHIFT-JIS"
168#define CS_SJIS "SJIS"
169#define CS_X_SJIS "X-SJIS"
170#define CS_ISO_2022_KR "ISO-2022-KR"
171#define CS_EUC_KR "EUC-KR"
172#define CS_KS_C_5601_1987 "ks_c_5601-1987"
173#define CS_ISO_2022_CN "ISO-2022-CN"
174#define CS_EUC_CN "EUC-CN"
175#define CS_GB2312 "GB2312"
176#define CS_GBK "GBK"
177#define CS_X_GBK "X-GBK"
178#define CS_EUC_TW "EUC-TW"
179#define CS_BIG5 "Big5"
180#define CS_BIG5_HKSCS "BIG5-HKSCS"
181#define CS_TIS_620 "TIS-620"
182#define CS_WINDOWS_874 "Windows-874"
183#define CS_GEORGIAN_PS "GEORGIAN-PS"
184#define CS_TCVN5712_1 "TCVN5712-1"
185#define CS_ISO_8859_16 "ISO-8859-16"
186#define CS_UTF_16 "UTF-16"
187#define CS_UTF_16BE "UTF-16BE"
188#define CS_UTF_16LE "UTF-16LE"
189
190#define C_INTERNAL C_UTF_8
191#define CS_INTERNAL CS_UTF_8
192
193//void conv_mb_alnum(gchar *str);
194
195CharSet conv_guess_ja_encoding (const gchar *str);
196
197gchar *conv_utf8todisp (const gchar *inbuf,
198 gint *error);
199gchar *conv_localetodisp (const gchar *inbuf,
200 gint *error);
201
202CodeConverter *conv_code_converter_new (const gchar *src_encoding,
203 const gchar *dest_encoding);
204void conv_code_converter_destroy (CodeConverter *conv);
205gchar *conv_convert (CodeConverter *conv,
206 const gchar *inbuf);
207
208#define conv_codeset_strdup(inbuf, src_code, dest_code) \
209 (conv_codeset_strdup_full(inbuf, src_code, dest_code, NULL))
210
211gchar *conv_codeset_strdup_full (const gchar *inbuf,
212 const gchar *src_encoding,
213 const gchar *dest_encoding,
214 gint *error);
215
216CodeConvFunc conv_get_code_conv_func (const gchar *src_encoding,
217 const gchar *dest_encoding);
218
219gchar *conv_iconv_strdup (const gchar *inbuf,
220 const gchar *src_encoding,
221 const gchar *dest_encoding,
222 gint *error);
223gchar *conv_iconv_strdup_with_cd (const gchar *inbuf,
224 iconv_t cd,
225 gint *error);
226
227const gchar *conv_get_charset_str (CharSet charset);
228CharSet conv_get_charset_from_str (const gchar *charset);
229CharSet conv_get_locale_charset (void);
230const gchar *conv_get_locale_charset_str (void);
231CharSet conv_get_internal_charset (void);
232const gchar *conv_get_internal_charset_str (void);
233CharSet conv_get_outgoing_charset (void);
234const gchar *conv_get_outgoing_charset_str (void);
235gboolean conv_is_multibyte_encoding (CharSet encoding);
236
237const gchar *conv_get_current_locale (void);
238gboolean conv_is_ja_locale (void);
239
240void conv_set_autodetect_type (ConvADType type);
241ConvADType conv_get_autodetect_type (void);
242
243gchar *conv_unmime_header (const gchar *str,
244 const gchar *default_encoding);
245void conv_encode_header (gchar *dest,
246 gint len,
247 const gchar *src,
248 gint header_len,
249 gboolean addr_field,
250 const gchar *out_encoding);
251gchar *conv_encode_filename (const gchar *src,
252 const gchar *param_name,
253 const gchar *out_encoding);
254
255gint conv_copy_file (const gchar *src,
256 const gchar *dest,
257 const gchar *src_encoding);
258gint conv_copy_dir (const gchar *src,
259 const gchar *dest,
260 const gchar *src_encoding);
261
262CharSet conv_check_file_encoding (const gchar *file);
263
264gchar *conv_filename_from_utf8 (const gchar *utf8_file);
265gchar *conv_filename_to_utf8 (const gchar *fs_file);
266
267#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <stdio.h>
28#include <string.h>
29#include <stdlib.h>
30#include <errno.h>
31
32#include "customheader.h"
33#include "prefs.h"
34#include "prefs_account.h"
35#include "utils.h"
36
37
38void custom_header_read_config(PrefsAccount *ac)
39{
40 gchar *rcpath;
41 FILE *fp;
42 gchar buf[PREFSBUFSIZE];
43 CustomHeader *ch;
44
45 debug_print("Reading custom header configuration...\n");
46
47 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
48 CUSTOM_HEADER_RC, NULL);
49 if ((fp = g_fopen(rcpath, "rb")) == NULL) {
50 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
51 g_free(rcpath);
52 ac->customhdr_list = NULL;
53 return;
54 }
55 g_free(rcpath);
56
57 /* remove all previous headers list */
58 while (ac->customhdr_list != NULL) {
59 ch = (CustomHeader *)ac->customhdr_list->data;
60 custom_header_free(ch);
61 ac->customhdr_list = g_slist_remove(ac->customhdr_list, ch);
62 }
63
64 while (fgets(buf, sizeof(buf), fp) != NULL) {
65 ch = custom_header_read_str(buf);
66 if (ch) {
67 if (ch->account_id == ac->account_id) {
68 ac->customhdr_list =
69 g_slist_append(ac->customhdr_list, ch);
70 } else
71 custom_header_free(ch);
72 }
73 }
74
75 fclose(fp);
76}
77
78void custom_header_write_config(PrefsAccount *ac)
79{
80 gchar *rcpath;
81 PrefFile *pfile;
82 GSList *cur;
83 gchar buf[PREFSBUFSIZE];
84 FILE * fp;
85 CustomHeader *ch;
86
87 GSList *all_hdrs = NULL;
88
89 debug_print("Writing custom header configuration...\n");
90
91 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
92 CUSTOM_HEADER_RC, NULL);
93
94 if ((fp = g_fopen(rcpath, "rb")) == NULL) {
95 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
96 } else {
97 all_hdrs = NULL;
98
99 while (fgets(buf, sizeof(buf), fp) != NULL) {
100 ch = custom_header_read_str(buf);
101 if (ch) {
102 if (ch->account_id != ac->account_id)
103 all_hdrs =
104 g_slist_append(all_hdrs, ch);
105 else
106 custom_header_free(ch);
107 }
108 }
109
110 fclose(fp);
111 }
112
113 if ((pfile = prefs_file_open(rcpath)) == NULL) {
114 g_warning("failed to write configuration to file\n");
115 g_free(rcpath);
116 return;
117 }
118
119 for (cur = all_hdrs; cur != NULL; cur = cur->next) {
120 CustomHeader *hdr = (CustomHeader *)cur->data;
121 gchar *chstr;
122
123 chstr = custom_header_get_str(hdr);
124 if (fputs(chstr, pfile->fp) == EOF ||
125 fputc('\n', pfile->fp) == EOF) {
126 FILE_OP_ERROR(rcpath, "fputs || fputc");
127 prefs_file_close_revert(pfile);
128 g_free(rcpath);
129 g_free(chstr);
130 return;
131 }
132 g_free(chstr);
133 }
134
135 for (cur = ac->customhdr_list; cur != NULL; cur = cur->next) {
136 CustomHeader *hdr = (CustomHeader *)cur->data;
137 gchar *chstr;
138
139 chstr = custom_header_get_str(hdr);
140 if (fputs(chstr, pfile->fp) == EOF ||
141 fputc('\n', pfile->fp) == EOF) {
142 FILE_OP_ERROR(rcpath, "fputs || fputc");
143 prefs_file_close_revert(pfile);
144 g_free(rcpath);
145 g_free(chstr);
146 return;
147 }
148 g_free(chstr);
149 }
150
151 g_free(rcpath);
152
153 while (all_hdrs != NULL) {
154 ch = (CustomHeader *)all_hdrs->data;
155 custom_header_free(ch);
156 all_hdrs = g_slist_remove(all_hdrs, ch);
157 }
158
159 if (prefs_file_close(pfile) < 0) {
160 g_warning("failed to write configuration to file\n");
161 return;
162 }
163}
164
165gchar *custom_header_get_str(CustomHeader *ch)
166{
167 return g_strdup_printf("%i:%s: %s",
168 ch->account_id, ch->name,
169 ch->value ? ch->value : "");
170}
171
172CustomHeader *custom_header_read_str(const gchar *buf)
173{
174 CustomHeader *ch;
175 gchar *account_id_str;
176 gint id;
177 gchar *name;
178 gchar *value;
179 gchar *tmp;
180
181 Xstrdup_a(tmp, buf, return NULL);
182
183 account_id_str = tmp;
184
185 name = strchr(account_id_str, ':');
186 if (!name)
187 return NULL;
188 else {
189 gchar *endp;
190
191 *name++ = '\0';
192 id = strtol(account_id_str, &endp, 10);
193 if (*endp != '\0') return NULL;
194 }
195
196 value = strchr(name, ':');
197 if (!value) return NULL;
198
199 *value++ = '\0';
200
201 g_strstrip(name);
202 g_strstrip(value);
203
204 ch = g_new0(CustomHeader, 1);
205 ch->account_id = id;
206 ch->name = *name ? g_strdup(name) : NULL;
207 ch->value = *value ? g_strdup(value) : NULL;
208
209 return ch;
210}
211
212CustomHeader *custom_header_find(GSList *header_list, const gchar *header)
213{
214 GSList *cur;
215 CustomHeader *chdr;
216
217 for (cur = header_list; cur != NULL; cur = cur->next) {
218 chdr = (CustomHeader *)cur->data;
219 if (!g_ascii_strcasecmp(chdr->name, header))
220 return chdr;
221 }
222
223 return NULL;
224}
225
226void custom_header_free(CustomHeader *ch)
227{
228 if (!ch) return;
229
230 g_free(ch->name);
231 g_free(ch->value);
232 g_free(ch);
233}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __CUSTOMHEADER_H__
21#define __CUSTOMHEADER_H__
22
23#include <glib.h>
24
25typedef struct _CustomHeader CustomHeader;
26
27#include "prefs_account.h"
28
29struct _CustomHeader
30{
31 gint account_id;
32 gchar *name;
33 gchar *value;
34};
35
36void custom_header_read_config (PrefsAccount *ac);
37void custom_header_write_config (PrefsAccount *ac);
38
39gchar *custom_header_get_str (CustomHeader *ch);
40CustomHeader *custom_header_read_str (const gchar *buf);
41CustomHeader *custom_header_find (GSList *header_list,
42 const gchar *header);
43void custom_header_free (CustomHeader *ch);
44
45#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __DEFS_H__
21#define __DEFS_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28
29#if HAVE_PATHS_H
30# include <paths.h>
31#endif
32
33#if HAVE_SYS_PARAM_H
34# include <sys/param.h>
35#endif
36
37#define INBOX_DIR "inbox"
38#define OUTBOX_DIR "sent"
39#define QUEUE_DIR "queue"
40#define DRAFT_DIR "draft"
41#define TRASH_DIR "trash"
42#define JUNK_DIR "junk"
43#if defined(G_OS_WIN32) || defined(__APPLE__)
44# define RC_DIR "Sylpheed"
45#else
46# define RC_DIR ".sylpheed-2.0"
47#endif
48#define OLD_RC_DIR ".sylpheed"
49#define NEWS_CACHE_DIR "newscache"
50#define IMAP_CACHE_DIR "imapcache"
51#define MIME_TMP_DIR "mimetmp"
52#define COMMON_RC "sylpheedrc"
53#define ACCOUNT_RC "accountrc"
54#define FILTER_RC "filterrc"
55#define FILTER_LIST "filter.xml"
56#define FILTER_HEADER_RC "filterheaderrc"
57#define CUSTOM_HEADER_RC "customheaderrc"
58#define DISPLAY_HEADER_RC "dispheaderrc"
59#define MENU_RC "menurc"
60#define ACTIONS_RC "actionsrc"
61#define COMMAND_HISTORY "command_history"
62#define TEMPLATE_DIR "templates"
63#define TMP_DIR "tmp"
64#define UIDL_DIR "uidl"
65#define PLUGIN_DIR "plugins"
66#define NEWSGROUP_LIST ".newsgroup_list"
67#define ADDRESS_BOOK "addressbook.xml"
68#define MANUAL_HTML_INDEX "sylpheed.html"
69#define FAQ_HTML_INDEX "sylpheed-faq.html"
70#define HOMEPAGE_URI "http://sylpheed.sraoss.jp/"
71#define DOWNLOAD_URI "http://sylpheed.sraoss.jp/download.php"
72#define VERSION_URI "http://sylpheed.sraoss.jp/version.txt"
73#define PLUGIN_HOMEPAGE_URI "http://sylpheed.sraoss.jp/en/plugin.html"
74#define PLUGIN_VERSION_URI "http://sylpheed.sraoss.jp/plugin_version.txt"
75#define FOLDER_LIST "folderlist.xml"
76#define CACHE_FILE ".sylpheed_cache"
77#define MARK_FILE ".sylpheed_mark"
78#define SEARCH_CACHE "search_cache"
79#define CACHE_VERSION 0x21
80#define MARK_VERSION 2
81#define SEARCH_CACHE_VERSION 1
82
83#ifdef G_OS_WIN32
84# define REMOTE_CMD_PORT 50215
85#endif
86
87#ifdef G_OS_WIN32
88# define DEFAULT_SIGNATURE "signature.txt"
89#else
90# define DEFAULT_SIGNATURE ".signature"
91#endif
92#define DEFAULT_INC_PATH "/usr/bin/mh/inc"
93#define DEFAULT_INC_PROGRAM "inc"
94/* #define DEFAULT_INC_PATH "/usr/bin/imget" */
95/* #define DEFAULT_INC_PROGRAM "imget" */
96#define DEFAULT_SENDMAIL_CMD "/usr/sbin/sendmail -t -i"
97#ifdef __APPLE__
98# define DEFAULT_BROWSER_CMD "open '%s'"
99#else
100# define DEFAULT_BROWSER_CMD "xdg-open '%s'"
101#endif
102
103#ifdef _PATH_MAILDIR
104# define DEFAULT_SPOOL_PATH _PATH_MAILDIR
105#else
106# define DEFAULT_SPOOL_PATH "/var/spool/mail"
107#endif
108
109#define BUFFSIZE 8192
110
111#ifndef MAXPATHLEN
112# define MAXPATHLEN 4095
113#endif
114
115#define DEFAULT_HEIGHT 460
116#define DEFAULT_FOLDERVIEW_WIDTH 179
117#define DEFAULT_MAINVIEW_WIDTH 600
118#define DEFAULT_SUMMARY_HEIGHT 140
119#define DEFAULT_HEADERVIEW_HEIGHT 40
120#define DEFAULT_COMPOSE_HEIGHT 560
121#define BORDER_WIDTH 2
122#define CTREE_INDENT 18
123#define FOLDER_SPACING 4
124#define MAX_ENTRY_LENGTH 8191
125#define COLOR_DIM 35000
126#define UI_REFRESH_INTERVAL 50000 /* usec */
127#define FOLDER_UPDATE_INTERVAL 1500 /* msec */
128#define PROGRESS_UPDATE_INTERVAL 200 /* msec */
129#define SESSION_TIMEOUT_INTERVAL 60 /* sec */
130#define MAX_HISTORY_SIZE 16
131
132#define DEFAULT_MESSAGE_FONT "Monospace 12"
133
134#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include <glib.h>
25
26#include "displayheader.h"
27
28gchar *display_header_prop_get_str(DisplayHeaderProp *dp)
29{
30 return g_strconcat(dp->hidden ? "-" : "", dp->name, NULL);
31}
32
33DisplayHeaderProp *display_header_prop_read_str(gchar *buf)
34{
35 DisplayHeaderProp *dp;
36
37 dp = g_new0(DisplayHeaderProp, 1);
38
39 dp->hidden = FALSE;
40 if (*buf == '-') {
41 dp->hidden = TRUE;
42 buf++;
43 }
44 if (*buf == '\0') {
45 g_free(dp);
46 return NULL;
47 }
48 dp->name = g_strdup(buf);
49
50 return dp;
51}
52
53void display_header_prop_free(DisplayHeaderProp *dp)
54{
55 if (!dp) return;
56
57 g_free(dp->name);
58 g_free(dp);
59}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __DISPLAYHEADER_H__
21#define __DISPLAYHEADER_H__
22
23#include <glib.h>
24
25typedef struct _DisplayHeaderProp DisplayHeaderProp;
26
27struct _DisplayHeaderProp
28{
29 gchar *name;
30 gboolean hidden;
31};
32
33gchar *display_header_prop_get_str (DisplayHeaderProp *dp);
34DisplayHeaderProp *display_header_prop_read_str (gchar *buf);
35void display_header_prop_free (DisplayHeaderProp *dp);
36
37#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __ENUMS_H__
21#define __ENUMS_H__
22
23typedef enum
24{
25 TOOLBAR_NONE,
26 TOOLBAR_ICON,
27 TOOLBAR_TEXT,
28 TOOLBAR_BOTH,
29 TOOLBAR_BOTH_HORIZ
30} ToolbarStyle;
31
32typedef enum
33{
34 LAYOUT_NORMAL,
35 LAYOUT_VERTICAL,
36 LAYOUT_VERTICAL_DOUBLE,
37 LAYOUT_WIDE_MESSAGE,
38 LAYOUT_WIDE_SUMMARY
39} LayoutType;
40
41typedef enum
42{
43 S_COL_MARK,
44 S_COL_UNREAD,
45 S_COL_MIME,
46 S_COL_SUBJECT,
47 S_COL_FROM,
48 S_COL_DATE,
49 S_COL_SIZE,
50 S_COL_NUMBER,
51 S_COL_TO,
52
53 S_COL_MSG_INFO,
54
55 S_COL_LABEL,
56 S_COL_TDATE,
57
58 S_COL_FOREGROUND,
59 S_COL_BOLD,
60
61 N_SUMMARY_COLS
62} SummaryColumnType;
63
64#define N_SUMMARY_VISIBLE_COLS S_COL_MSG_INFO
65
66#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2015 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <string.h>
29#include <strings.h>
30#include <stdlib.h>
31#include <sys/types.h>
32#if USE_ONIGURUMA
33# include <oniguruma.h>
34#elif HAVE_REGEX_H
35# include <regex.h>
36#endif
37#include <time.h>
38
39#include "filter.h"
40#include "procmsg.h"
41#include "procheader.h"
42#include "folder.h"
43#include "utils.h"
44#include "xml.h"
45#include "prefs.h"
46#include "prefs_common.h"
47#include "prefs_account.h"
48#include "account.h"
49
50typedef enum
51{
52 FLT_O_CONTAIN = 1 << 0,
53 FLT_O_CASE_SENS = 1 << 1,
54 FLT_O_REGEX = 1 << 2
55} FilterOldFlag;
56
57static FilterInAddressBookFunc default_addrbook_func = NULL;
58
59static gboolean filter_match_cond (FilterCond *cond,
60 MsgInfo *msginfo,
61 GSList *hlist,
62 FilterInfo *fltinfo);
63static gboolean filter_match_header_cond(FilterCond *cond,
64 GSList *hlist);
65static gboolean filter_match_in_addressbook
66 (FilterCond *cond,
67 GSList *hlist,
68 FilterInfo *fltinfo);
69
70static void filter_cond_free (FilterCond *cond);
71static void filter_action_free (FilterAction *action);
72
73
74gint filter_apply(GSList *fltlist, const gchar *file, FilterInfo *fltinfo)
75{
76 MsgInfo *msginfo;
77 gint ret = 0;
78
79 g_return_val_if_fail(file != NULL, -1);
80 g_return_val_if_fail(fltinfo != NULL, -1);
81
82 if (!fltlist) return 0;
83
84 msginfo = procheader_parse_file(file, fltinfo->flags, FALSE);
85 if (!msginfo) return 0;
86 msginfo->file_path = g_strdup(file);
87
88 /* inherit MIME flag */
89 fltinfo->flags.tmp_flags =
90 (fltinfo->flags.tmp_flags & ~MSG_CACHED_FLAG_MASK) |
91 (msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK);
92
93 ret = filter_apply_msginfo(fltlist, msginfo, fltinfo);
94
95 procmsg_msginfo_free(msginfo);
96
97 return ret;
98}
99
100gint filter_apply_msginfo(GSList *fltlist, MsgInfo *msginfo,
101 FilterInfo *fltinfo)
102{
103 gchar *file;
104 GSList *hlist, *cur;
105 FilterRule *rule;
106 gint ret = 0;
107
108 g_return_val_if_fail(msginfo != NULL, -1);
109 g_return_val_if_fail(fltinfo != NULL, -1);
110
111 fltinfo->error = FLT_ERROR_OK;
112
113 if (!fltlist) return 0;
114
115 file = procmsg_get_message_file(msginfo);
116 if (!file)
117 return -1;
118 hlist = procheader_get_header_list_from_file(file);
119 if (!hlist) {
120 g_free(file);
121 return 0;
122 }
123
124 procmsg_set_auto_decrypt_message(FALSE);
125
126 for (cur = fltlist; cur != NULL; cur = cur->next) {
127 gboolean matched;
128
129 rule = (FilterRule *)cur->data;
130 if (!rule->enabled) continue;
131 matched = filter_match_rule(rule, msginfo, hlist, fltinfo);
132 if (fltinfo->error != FLT_ERROR_OK) {
133 g_warning("filter_match_rule() returned error (code: %d)\n", fltinfo->error);
134 }
135 if (matched) {
136 debug_print("filter-log: %s: rule [%s] matched\n",
137 G_STRFUNC, rule->name ? rule->name : "(No name)");
138 ret = filter_action_exec(rule, msginfo, file, fltinfo);
139 if (ret < 0) {
140 g_warning("filter_action_exec() returned error (code: %d)\n", fltinfo->error);
141 break;
142 }
143 if (fltinfo->drop_done == TRUE ||
144 fltinfo->actions[FLT_ACTION_STOP_EVAL] == TRUE)
145 break;
146 }
147 }
148
149 procmsg_set_auto_decrypt_message(TRUE);
150
151 procheader_header_list_destroy(hlist);
152 g_free(file);
153
154 return ret;
155}
156
157gint filter_action_exec(FilterRule *rule, MsgInfo *msginfo, const gchar *file,
158 FilterInfo *fltinfo)
159{
160 FolderItem *dest_folder = NULL;
161 FilterAction *action;
162 GSList *cur;
163 gchar *cmdline;
164 gboolean copy_to_self = FALSE;
165 gint ret;
166
167 g_return_val_if_fail(rule != NULL, -1);
168 g_return_val_if_fail(msginfo != NULL, -1);
169 g_return_val_if_fail(file != NULL, -1);
170 g_return_val_if_fail(fltinfo != NULL, -1);
171
172 for (cur = rule->action_list; cur != NULL; cur = cur->next) {
173 action = (FilterAction *)cur->data;
174
175 switch (action->type) {
176 case FLT_ACTION_MARK:
177 debug_print("filter_action_exec(): mark\n");
178 MSG_SET_PERM_FLAGS(fltinfo->flags, MSG_MARKED);
179 fltinfo->actions[action->type] = TRUE;
180 break;
181 case FLT_ACTION_COLOR_LABEL:
182 debug_print("filter_action_exec(): color label: %d\n",
183 action->int_value);
184 MSG_UNSET_PERM_FLAGS(fltinfo->flags,
185 MSG_CLABEL_FLAG_MASK);
186 MSG_SET_COLORLABEL_VALUE(fltinfo->flags,
187 action->int_value);
188 fltinfo->actions[action->type] = TRUE;
189 break;
190 case FLT_ACTION_MARK_READ:
191 debug_print("filter_action_exec(): mark as read\n");
192 if (msginfo->folder) {
193 if (MSG_IS_NEW(fltinfo->flags))
194 msginfo->folder->new--;
195 if (MSG_IS_UNREAD(fltinfo->flags))
196 msginfo->folder->unread--;
197 }
198 MSG_UNSET_PERM_FLAGS(fltinfo->flags, MSG_NEW|MSG_UNREAD);
199 fltinfo->actions[action->type] = TRUE;
200 break;
201 case FLT_ACTION_EXEC:
202 cmdline = g_strconcat(action->str_value, " \"", file,
203 "\"", NULL);
204 ret = execute_command_line(cmdline, FALSE);
205 fltinfo->last_exec_exit_status = ret;
206 if (ret == -1) {
207 fltinfo->error = FLT_ERROR_EXEC_FAILED;
208 g_warning("filter_action_exec: cannot execute command: %s", cmdline);
209 g_free(cmdline);
210 return -1;
211 }
212 g_free(cmdline);
213 fltinfo->actions[action->type] = TRUE;
214 break;
215 case FLT_ACTION_EXEC_ASYNC:
216 cmdline = g_strconcat(action->str_value, " \"", file,
217 "\"", NULL);
218 ret = execute_command_line(cmdline, TRUE);
219 fltinfo->last_exec_exit_status = ret;
220 if (ret == -1) {
221 fltinfo->error = FLT_ERROR_EXEC_FAILED;
222 g_warning("filter_action_exec: cannot execute command: %s", cmdline);
223 g_free(cmdline);
224 return -1;
225 }
226 g_free(cmdline);
227 fltinfo->actions[action->type] = TRUE;
228 break;
229 default:
230 break;
231 }
232 }
233
234 for (cur = rule->action_list; cur != NULL; cur = cur->next) {
235 action = (FilterAction *)cur->data;
236
237 switch (action->type) {
238 case FLT_ACTION_MOVE:
239 case FLT_ACTION_COPY:
240 dest_folder = folder_find_item_from_identifier
241 (action->str_value);
242 if (!dest_folder) {
243 g_warning("dest folder '%s' not found\n",
244 action->str_value);
245 fltinfo->error = FLT_ERROR_ERROR;
246 return -1;
247 }
248 debug_print("filter_action_exec(): %s: dest_folder = %s\n",
249 action->type == FLT_ACTION_COPY ?
250 "copy" : "move", action->str_value);
251
252 if (msginfo->folder) {
253 gint val;
254
255 /* local filtering */
256 if (msginfo->folder == dest_folder)
257 copy_to_self = TRUE;
258 else {
259 if (action->type == FLT_ACTION_COPY) {
260 MsgFlags save_flags;
261
262 save_flags = msginfo->flags;
263 msginfo->flags = fltinfo->flags;
264 val = folder_item_copy_msg
265 (dest_folder, msginfo);
266 msginfo->flags = save_flags;
267 if (val == -1) {
268 fltinfo->error = FLT_ERROR_ERROR;
269 return -1;
270 }
271 }
272 fltinfo->actions[action->type] = TRUE;
273 }
274 } else {
275 MsgFlags save_flags;
276
277 save_flags = msginfo->flags;
278 msginfo->flags = fltinfo->flags;
279 if (folder_item_add_msg_msginfo
280 (dest_folder, msginfo, FALSE) < 0) {
281 msginfo->flags = save_flags;
282 fltinfo->error = FLT_ERROR_ERROR;
283 return -1;
284 }
285 msginfo->flags = save_flags;
286 fltinfo->actions[action->type] = TRUE;
287 }
288
289 fltinfo->dest_list = g_slist_append(fltinfo->dest_list,
290 dest_folder);
291 if (action->type == FLT_ACTION_MOVE) {
292 fltinfo->move_dest = dest_folder;
293 fltinfo->drop_done = TRUE;
294 }
295 break;
296 default:
297 break;
298 }
299 }
300
301 if (fltinfo->drop_done == TRUE)
302 return 0;
303
304 for (cur = rule->action_list; cur != NULL; cur = cur->next) {
305 action = (FilterAction *)cur->data;
306
307 switch (action->type) {
308 case FLT_ACTION_NOT_RECEIVE:
309 debug_print("filter_action_exec(): don't receive\n");
310 fltinfo->drop_done = TRUE;
311 fltinfo->actions[action->type] = TRUE;
312 return 0;
313 case FLT_ACTION_DELETE:
314 debug_print("filter_action_exec(): delete\n");
315 if (msginfo->folder) {
316 /* local filtering */
317 if (copy_to_self == FALSE)
318 fltinfo->actions[action->type] = TRUE;
319 } else
320 fltinfo->actions[action->type] = TRUE;
321
322 fltinfo->drop_done = TRUE;
323 return 0;
324 case FLT_ACTION_STOP_EVAL:
325 debug_print("filter_action_exec(): stop evaluation\n");
326 fltinfo->actions[action->type] = TRUE;
327 return 0;
328 default:
329 break;
330 }
331 }
332
333 return 0;
334}
335
336static gboolean strmatch_regex(const gchar *haystack, const gchar *needle)
337{
338#ifdef USE_ONIGURUMA
339 gint ret = 0;
340 OnigRegex reg;
341 OnigErrorInfo err_info;
342 const UChar *ptn = (const UChar *)needle;
343 const UChar *str = (const UChar *)haystack;
344 size_t haystack_len;
345
346 ret = onig_new(&reg, ptn, ptn + strlen(needle),
347 /* ONIG_OPTION_EXTEND requires spaces to be escaped */
348 /* ONIG_OPTION_IGNORECASE|ONIG_OPTION_EXTEND, */
349 ONIG_OPTION_IGNORECASE,
350 ONIG_ENCODING_UTF8, ONIG_SYNTAX_POSIX_EXTENDED,
351 &err_info);
352 if (ret != ONIG_NORMAL) {
353 g_warning("strmatch_regex: onig_new() failed: %d", ret);
354 return FALSE;
355 }
356
357 haystack_len = strlen(haystack);
358 ret = onig_search(reg, str, str + haystack_len,
359 str, str + haystack_len, NULL, 0);
360 onig_free(reg);
361
362 if (ret >= 0)
363 return TRUE;
364 else
365 return FALSE;
366#elif defined(HAVE_REGCOMP)
367 gint ret = 0;
368 regex_t preg;
369
370 ret = regcomp(&preg, needle, REG_ICASE|REG_EXTENDED);
371 if (ret != 0)
372 return FALSE;
373
374 ret = regexec(&preg, haystack, 0, NULL, 0);
375 regfree(&preg);
376
377 if (ret == 0)
378 return TRUE;
379 else
380 return FALSE;
381#else
382 return FALSE;
383#endif
384}
385
386gboolean filter_match_rule(FilterRule *rule, MsgInfo *msginfo, GSList *hlist,
387 FilterInfo *fltinfo)
388{
389 FilterCond *cond;
390 GSList *cur;
391 gboolean matched;
392
393 g_return_val_if_fail(rule->cond_list != NULL, FALSE);
394
395 switch (rule->timing) {
396 case FLT_TIMING_ANY:
397 break;
398 case FLT_TIMING_ON_RECEIVE:
399 if (msginfo->folder != NULL)
400 return FALSE;
401 break;
402 case FLT_TIMING_MANUAL:
403 if (msginfo->folder == NULL)
404 return FALSE;
405 break;
406 default:
407 break;
408 }
409
410 if (rule->bool_op == FLT_AND) {
411 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
412 cond = (FilterCond *)cur->data;
413 if (cond->type >= FLT_COND_SIZE_GREATER) {
414 matched = filter_match_cond
415 (cond, msginfo, hlist, fltinfo);
416 if (matched == FALSE)
417 return FALSE;
418 }
419 }
420
421 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
422 cond = (FilterCond *)cur->data;
423 if (cond->type <= FLT_COND_TO_OR_CC) {
424 matched = filter_match_cond
425 (cond, msginfo, hlist, fltinfo);
426 if (matched == FALSE)
427 return FALSE;
428 }
429 }
430
431 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
432 cond = (FilterCond *)cur->data;
433 if (cond->type == FLT_COND_BODY ||
434 cond->type == FLT_COND_CMD_TEST) {
435 matched = filter_match_cond
436 (cond, msginfo, hlist, fltinfo);
437 if (matched == FALSE)
438 return FALSE;
439 }
440 }
441
442 return TRUE;
443 } else if (rule->bool_op == FLT_OR) {
444 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
445 cond = (FilterCond *)cur->data;
446 if (cond->type >= FLT_COND_SIZE_GREATER) {
447 matched = filter_match_cond
448 (cond, msginfo, hlist, fltinfo);
449 if (matched == TRUE)
450 return TRUE;
451 }
452 }
453
454 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
455 cond = (FilterCond *)cur->data;
456 if (cond->type <= FLT_COND_TO_OR_CC) {
457 matched = filter_match_cond
458 (cond, msginfo, hlist, fltinfo);
459 if (matched == TRUE)
460 return TRUE;
461 }
462 }
463
464 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
465 cond = (FilterCond *)cur->data;
466 if (cond->type == FLT_COND_BODY ||
467 cond->type == FLT_COND_CMD_TEST) {
468 matched = filter_match_cond
469 (cond, msginfo, hlist, fltinfo);
470 if (matched == TRUE)
471 return TRUE;
472 }
473 }
474
475 return FALSE;
476 }
477
478 return FALSE;
479}
480
481static gboolean filter_match_cond(FilterCond *cond, MsgInfo *msginfo,
482 GSList *hlist, FilterInfo *fltinfo)
483{
484 gint ret;
485 gboolean matched = FALSE;
486 gboolean not_match = FALSE;
487 gint64 timediff = 0;
488 gchar *file;
489 gchar *cmdline;
490 PrefsAccount *cond_ac;
491
492 switch (cond->type) {
493 case FLT_COND_HEADER:
494 if (cond->match_type == FLT_IN_ADDRESSBOOK)
495 return filter_match_in_addressbook(cond, hlist, fltinfo);
496 else
497 return filter_match_header_cond(cond, hlist);
498 case FLT_COND_ANY_HEADER:
499 return filter_match_header_cond(cond, hlist);
500 case FLT_COND_TO_OR_CC:
501 if (cond->match_type == FLT_IN_ADDRESSBOOK)
502 return filter_match_in_addressbook(cond, hlist, fltinfo);
503 else
504 return filter_match_header_cond(cond, hlist);
505 case FLT_COND_BODY:
506 matched = procmime_find_string(msginfo, cond->str_value,
507 cond->match_func);
508 break;
509 case FLT_COND_CMD_TEST:
510 file = procmsg_get_message_file(msginfo);
511 if (!file)
512 return FALSE;
513 cmdline = g_strconcat(cond->str_value, " \"", file, "\"", NULL);
514 ret = execute_command_line_async_wait(cmdline);
515 fltinfo->last_exec_exit_status = ret;
516 matched = (ret == 0);
517 if (ret == -1)
518 fltinfo->error = FLT_ERROR_EXEC_FAILED;
519 g_free(cmdline);
520 g_free(file);
521 break;
522 case FLT_COND_SIZE_GREATER:
523 matched = (msginfo->size > cond->int_value * 1024);
524 break;
525 case FLT_COND_AGE_GREATER:
526 timediff = time(NULL) - msginfo->date_t;
527 matched = (timediff > cond->int_value * 24 * 60 * 60);
528 break;
529 case FLT_COND_UNREAD:
530 matched = MSG_IS_UNREAD(msginfo->flags);
531 break;
532 case FLT_COND_MARK:
533 matched = MSG_IS_MARKED(msginfo->flags);
534 break;
535 case FLT_COND_COLOR_LABEL:
536 matched = (MSG_GET_COLORLABEL_VALUE(msginfo->flags) != 0);
537 break;
538 case FLT_COND_MIME:
539 matched = MSG_IS_MIME(msginfo->flags);
540 break;
541 case FLT_COND_ACCOUNT:
542 cond_ac = account_find_from_id(cond->int_value);
543 matched = (cond_ac != NULL && cond_ac == fltinfo->account);
544 break;
545 default:
546 g_warning("filter_match_cond(): unknown condition: %d\n",
547 cond->type);
548 fltinfo->error = FLT_ERROR_ERROR;
549 return FALSE;
550 }
551
552 if (FLT_IS_NOT_MATCH(cond->match_flag)) {
553 not_match = TRUE;
554 matched = !matched;
555 }
556
557 if (matched && get_debug_mode()) {
558 gchar *sv = cond->str_value ? cond->str_value : "";
559 gchar *nm = not_match ? " (reverse match)" : "";
560
561 switch (cond->type) {
562 case FLT_COND_BODY:
563 debug_print("filter-log: %s: BODY, str_value: [%s]%s\n", G_STRFUNC, sv, nm);
564 break;
565 case FLT_COND_CMD_TEST:
566 debug_print("filter-log: %s: CMD_TEST, str_value: [%s]%s\n", G_STRFUNC, sv, nm);
567 break;
568 case FLT_COND_SIZE_GREATER:
569 debug_print("filter-log: %s: SIZE_GREATER: %u %s %d (KB)%s\n", G_STRFUNC, msginfo->size, not_match ? "<=" : ">", cond->int_value, nm);
570 break;
571 case FLT_COND_AGE_GREATER:
572 debug_print("filter-log: %s: AGE_GREATER: %lld (sec) %s %d (day)%s\n", G_STRFUNC, timediff, not_match ? "<=" : ">", cond->int_value, nm);
573 break;
574 case FLT_COND_UNREAD:
575 debug_print("filter-log: %s: UNREAD%s\n", G_STRFUNC, nm);
576 break;
577 case FLT_COND_MARK:
578 debug_print("filter-log: %s: MARK%s\n", G_STRFUNC, nm);
579 break;
580 case FLT_COND_COLOR_LABEL:
581 debug_print("filter-log: %s: COLOR_LABEL%s\n", G_STRFUNC, nm);
582 break;
583 case FLT_COND_MIME:
584 debug_print("filter-log: %s: MIME%s\n", G_STRFUNC, nm);
585 break;
586 case FLT_COND_ACCOUNT:
587 debug_print("filter-log: %s: ACCOUNT [%d]%s\n", G_STRFUNC, cond->int_value, nm);
588 break;
589 default:
590 break;
591 }
592 }
593
594 return matched;
595}
596
597static gboolean filter_match_header_cond(FilterCond *cond, GSList *hlist)
598{
599 gboolean matched = FALSE;
600 gboolean not_match = FALSE;
601 GSList *cur;
602 Header *header;
603
604 for (cur = hlist; cur != NULL; cur = cur->next) {
605 header = (Header *)cur->data;
606
607 switch (cond->type) {
608 case FLT_COND_HEADER:
609 if (!g_ascii_strcasecmp
610 (header->name, cond->header_name)) {
611 if (!cond->str_value ||
612 cond->match_func(header->body,
613 cond->str_value))
614 matched = TRUE;
615 }
616 break;
617 case FLT_COND_ANY_HEADER:
618 if (!cond->str_value ||
619 cond->match_func(header->body, cond->str_value))
620 matched = TRUE;
621 break;
622 case FLT_COND_TO_OR_CC:
623 if (!g_ascii_strcasecmp(header->name, "To") ||
624 !g_ascii_strcasecmp(header->name, "Cc")) {
625 if (!cond->str_value ||
626 cond->match_func(header->body,
627 cond->str_value))
628 matched = TRUE;
629 }
630 break;
631 default:
632 break;
633 }
634
635 if (matched == TRUE)
636 break;
637 }
638
639 if (FLT_IS_NOT_MATCH(cond->match_flag)) {
640 not_match = TRUE;
641 matched = !matched;
642 }
643
644 if (matched && get_debug_mode()) {
645 gchar *sv = cond->str_value ? cond->str_value : "";
646 gchar *nm = not_match ? " (reverse match)" : "";
647
648 switch (cond->type) {
649 case FLT_COND_HEADER:
650 debug_print("filter-log: %s: HEADER [%s], str_value: [%s]%s\n", G_STRFUNC, cond->header_name, sv, nm);
651 break;
652 case FLT_COND_ANY_HEADER:
653 debug_print("filter-log: %s: ANY_HEADER, str_value: [%s]%s\n", G_STRFUNC, sv, nm);
654 break;
655 case FLT_COND_TO_OR_CC:
656 debug_print("filter-log: %s: TO_OR_CC, str_value: [%s]%s\n", G_STRFUNC, sv, nm);
657 break;
658 default:
659 break;
660 }
661 }
662
663 return matched;
664}
665
666static gboolean filter_match_in_addressbook(FilterCond *cond, GSList *hlist,
667 FilterInfo *fltinfo)
668{
669 gboolean matched = FALSE;
670 gboolean not_match = FALSE;
671 GSList *cur;
672 Header *header;
673
674 if (!default_addrbook_func)
675 return FALSE;
676 if (cond->type != FLT_COND_HEADER && cond->type != FLT_COND_TO_OR_CC)
677 return FALSE;
678
679 for (cur = hlist; cur != NULL; cur = cur->next) {
680 header = (Header *)cur->data;
681
682 if (cond->type == FLT_COND_HEADER) {
683 if (!g_ascii_strcasecmp
684 (header->name, cond->header_name)) {
685 if (default_addrbook_func(header->body))
686 matched = TRUE;
687 }
688 } else if (cond->type == FLT_COND_TO_OR_CC) {
689 if (!g_ascii_strcasecmp(header->name, "To") ||
690 !g_ascii_strcasecmp(header->name, "Cc")) {
691 if (default_addrbook_func(header->body))
692 matched = TRUE;
693 }
694 }
695
696 if (matched == TRUE)
697 break;
698 }
699
700 if (FLT_IS_NOT_MATCH(cond->match_flag)) {
701 not_match = TRUE;
702 matched = !matched;
703 }
704
705 if (matched && get_debug_mode()) {
706 gchar *nm = not_match ? " (reverse match)" : "";
707
708 switch (cond->type) {
709 case FLT_COND_HEADER:
710 debug_print("filter-log: %s: HEADER [%s], IN_ADDRESSBOOK%s\n", G_STRFUNC, cond->header_name, nm);
711 break;
712 case FLT_COND_TO_OR_CC:
713 debug_print("filter-log: %s: TO_OR_CC, IN_ADDRESSBOOK%s\n", G_STRFUNC, nm);
714 break;
715 default:
716 break;
717 }
718 }
719
720 return matched;
721}
722
723gboolean filter_rule_requires_full_headers(FilterRule *rule)
724{
725 GSList *cur;
726
727 for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
728 FilterCond *cond = (FilterCond *)cur->data;
729 const gchar *name = cond->header_name;
730
731 if (cond->type == FLT_COND_HEADER && name) {
732 if (g_ascii_strcasecmp(name, "Date") != 0 &&
733 g_ascii_strcasecmp(name, "From") != 0 &&
734 g_ascii_strcasecmp(name, "To") != 0 &&
735 g_ascii_strcasecmp(name, "Newsgroups") != 0 &&
736 g_ascii_strcasecmp(name, "Subject") != 0)
737 return TRUE;
738 } else if (cond->type == FLT_COND_ANY_HEADER ||
739 cond->type == FLT_COND_TO_OR_CC)
740 return TRUE;
741 }
742
743 return FALSE;
744}
745
746#define RETURN_IF_TAG_NOT_MATCH(tag_name) \
747 if (strcmp2(xmlnode->tag->tag, tag_name) != 0) { \
748 g_warning("tag name != \"" tag_name "\"\n"); \
749 filter_cond_list_free(cond_list); \
750 filter_action_list_free(cond_list); \
751 return FALSE; \
752 } \
753
754#define STR_SWITCH(sw) { const gchar *sw_str = sw;
755#define STR_CASE_BEGIN(str) if (!strcmp(sw_str, str)) {
756#define STR_CASE(str) } else if (!strcmp(sw_str, str)) {
757#define STR_CASE_END } }
758
759static gboolean filter_xml_node_func(GNode *node, gpointer data)
760{
761 XMLNode *xmlnode;
762 GList *list;
763 const gchar *name = NULL;
764 FilterTiming timing = FLT_TIMING_ANY;
765 gboolean enabled = TRUE;
766 FilterRule *rule;
767 FilterBoolOp bool_op = FLT_OR;
768 const gchar *target_folder = NULL;
769 gboolean recursive = FALSE;
770 GSList *cond_list = NULL;
771 GSList *action_list = NULL;
772 GNode *child, *cond_child, *action_child;
773 GSList **fltlist = (GSList **)data;
774
775 if (g_node_depth(node) != 2) return FALSE;
776 g_return_val_if_fail(node->data != NULL, FALSE);
777
778 xmlnode = node->data;
779 RETURN_IF_TAG_NOT_MATCH("rule");
780
781 for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
782 XMLAttr *attr = (XMLAttr *)list->data;
783
784 if (!attr || !attr->name || !attr->value) continue;
785 if (!strcmp(attr->name, "name"))
786 name = attr->value;
787 else if (!strcmp(attr->name, "timing")) {
788 if (!strcmp(attr->value, "any"))
789 timing = FLT_TIMING_ANY;
790 else if (!strcmp(attr->value, "receive"))
791 timing = FLT_TIMING_ON_RECEIVE;
792 else if (!strcmp(attr->value, "manual"))
793 timing = FLT_TIMING_MANUAL;
794 } else if (!strcmp(attr->name, "enabled")) {
795 if (!strcmp(attr->value, "true"))
796 enabled = TRUE;
797 else
798 enabled = FALSE;
799 }
800 }
801
802 /* condition list */
803 child = node->children;
804 if (!child) {
805 g_warning("<rule> must have children\n");
806 return FALSE;
807 }
808 xmlnode = child->data;
809 RETURN_IF_TAG_NOT_MATCH("condition-list");
810 for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
811 XMLAttr *attr = (XMLAttr *)list->data;
812
813 if (attr && attr->name && attr->value &&
814 !strcmp(attr->name, "bool")) {
815 if (!strcmp(attr->value, "or"))
816 bool_op = FLT_OR;
817 else
818 bool_op = FLT_AND;
819 }
820 }
821
822 for (cond_child = child->children; cond_child != NULL;
823 cond_child = cond_child->next) {
824 const gchar *type = NULL;
825 const gchar *name = NULL;
826 const gchar *value = NULL;
827 gboolean case_sens = FALSE;
828 FilterCond *cond;
829 FilterCondType cond_type = FLT_COND_HEADER;
830 FilterMatchType match_type = FLT_CONTAIN;
831 FilterMatchFlag match_flag = 0;
832
833 xmlnode = cond_child->data;
834 if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue;
835
836 for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
837 XMLAttr *attr = (XMLAttr *)list->data;
838
839 if (!attr || !attr->name || !attr->value) continue;
840
841 STR_SWITCH(attr->name)
842 STR_CASE_BEGIN("type")
843 type = attr->value;
844 STR_CASE("name")
845 name = attr->value;
846 STR_CASE("case")
847 case_sens = TRUE;
848 STR_CASE("recursive")
849 if (!strcmp(attr->value, "true"))
850 recursive = TRUE;
851 else
852 recursive = FALSE;
853 STR_CASE_END
854 }
855
856 if (type) {
857 filter_rule_match_type_str_to_enum
858 (type, &match_type, &match_flag);
859 }
860 if (case_sens)
861 match_flag |= FLT_CASE_SENS;
862 value = xmlnode->element;
863
864 STR_SWITCH(xmlnode->tag->tag)
865 STR_CASE_BEGIN("match-header")
866 cond_type = FLT_COND_HEADER;
867 STR_CASE("match-any-header")
868 cond_type = FLT_COND_ANY_HEADER;
869 STR_CASE("match-to-or-cc")
870 cond_type = FLT_COND_TO_OR_CC;
871 STR_CASE("match-body-text")
872 cond_type = FLT_COND_BODY;
873 STR_CASE("command-test")
874 cond_type = FLT_COND_CMD_TEST;
875 STR_CASE("size")
876 cond_type = FLT_COND_SIZE_GREATER;
877 STR_CASE("age")
878 cond_type = FLT_COND_AGE_GREATER;
879 STR_CASE("unread")
880 cond_type = FLT_COND_UNREAD;
881 STR_CASE("mark")
882 cond_type = FLT_COND_MARK;
883 STR_CASE("color-label")
884 cond_type = FLT_COND_COLOR_LABEL;
885 STR_CASE("mime")
886 cond_type = FLT_COND_MIME;
887 STR_CASE("account-id")
888 cond_type = FLT_COND_ACCOUNT;
889 STR_CASE("target-folder")
890 target_folder = value;
891 continue;
892 STR_CASE_END
893
894 cond = filter_cond_new(cond_type, match_type, match_flag,
895 name, value);
896 cond_list = g_slist_append(cond_list, cond);
897 }
898
899 /* action list */
900 child = child->next;
901 if (!child) {
902 g_warning("<rule> must have multiple children\n");
903 filter_cond_list_free(cond_list);
904 return FALSE;
905 }
906 xmlnode = child->data;
907 RETURN_IF_TAG_NOT_MATCH("action-list");
908
909 for (action_child = child->children; action_child != NULL;
910 action_child = action_child->next) {
911 FilterAction *action;
912 FilterActionType action_type = FLT_ACTION_NONE;
913
914 xmlnode = action_child->data;
915 if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue;
916
917 STR_SWITCH(xmlnode->tag->tag)
918 STR_CASE_BEGIN("move")
919 action_type = FLT_ACTION_MOVE;
920 STR_CASE("copy")
921 action_type = FLT_ACTION_COPY;
922 STR_CASE("not-receive")
923 action_type = FLT_ACTION_NOT_RECEIVE;
924 STR_CASE("delete")
925 action_type = FLT_ACTION_DELETE;
926 STR_CASE("exec")
927 action_type = FLT_ACTION_EXEC;
928 STR_CASE("exec-async")
929 action_type = FLT_ACTION_EXEC_ASYNC;
930 STR_CASE("mark")
931 action_type = FLT_ACTION_MARK;
932 STR_CASE("color-label")
933 action_type = FLT_ACTION_COLOR_LABEL;
934 STR_CASE("mark-as-read")
935 action_type = FLT_ACTION_MARK_READ;
936 STR_CASE("forward")
937 action_type = FLT_ACTION_FORWARD;
938 STR_CASE("forward-as-attachment")
939 action_type = FLT_ACTION_FORWARD_AS_ATTACHMENT;
940 STR_CASE("redirect")
941 action_type = FLT_ACTION_REDIRECT;
942 STR_CASE("stop-eval")
943 action_type = FLT_ACTION_STOP_EVAL;
944 STR_CASE_END
945
946 action = filter_action_new(action_type, xmlnode->element);
947 action_list = g_slist_append(action_list, action);
948 }
949
950 if (name && cond_list) {
951 rule = filter_rule_new(name, bool_op, cond_list, action_list);
952 rule->timing = timing;
953 rule->enabled = enabled;
954 if (target_folder) {
955 rule->target_folder = g_strdup(target_folder);
956 rule->recursive = recursive;
957 }
958 *fltlist = g_slist_prepend(*fltlist, rule);
959 }
960
961 return FALSE;
962}
963
964#undef RETURN_IF_TAG_NOT_MATCH
965#undef STR_SWITCH
966#undef STR_CASE_BEGIN
967#undef STR_CASE
968#undef STR_CASE_END
969
970GSList *filter_xml_node_to_filter_list(GNode *node)
971{
972 GSList *fltlist = NULL;
973
974 g_return_val_if_fail(node != NULL, NULL);
975
976 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2,
977 filter_xml_node_func, &fltlist);
978 fltlist = g_slist_reverse(fltlist);
979
980 return fltlist;
981}
982
983GSList *filter_read_file(const gchar *file)
984{
985 GNode *node;
986 GSList *list;
987
988 g_return_val_if_fail(file != NULL, NULL);
989
990 debug_print("Reading %s\n", file);
991
992 if (!is_file_exist(file))
993 return NULL;
994
995 node = xml_parse_file(file);
996 if (!node) {
997 g_warning("Can't parse %s\n", file);
998 return NULL;
999 }
1000
1001 list = filter_xml_node_to_filter_list(node);
1002
1003 xml_free_tree(node);
1004
1005 return list;
1006}
1007
1008void filter_read_config(void)
1009{
1010 gchar *rcpath;
1011
1012 debug_print("Reading filter configuration...\n");
1013
1014 /* remove all previous filter list */
1015 while (prefs_common.fltlist != NULL) {
1016 FilterRule *rule = (FilterRule *)prefs_common.fltlist->data;
1017
1018 filter_rule_free(rule);
1019 prefs_common.fltlist = g_slist_remove(prefs_common.fltlist,
1020 rule);
1021 }
1022
1023 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST,
1024 NULL);
1025 prefs_common.fltlist = filter_read_file(rcpath);
1026 g_free(rcpath);
1027}
1028
1029#define NODE_NEW(tag, text) \
1030 node = xml_node_new(xml_tag_new(tag), text)
1031#define ADD_ATTR(name, value) \
1032 xml_tag_add_attr(node->tag, xml_attr_new(name, value))
1033
1034void filter_write_file(GSList *list, const gchar *file)
1035{
1036 PrefFile *pfile;
1037 GSList *cur;
1038
1039 g_return_if_fail(file != NULL);
1040
1041 if ((pfile = prefs_file_open(file)) == NULL) {
1042 g_warning("failed to write filter configuration to file: %s\n",
1043 file);
1044 return;
1045 }
1046
1047 xml_file_put_xml_decl(pfile->fp);
1048 fputs("\n<filter>\n", pfile->fp);
1049
1050 for (cur = list; cur != NULL; cur = cur->next) {
1051 FilterRule *rule = (FilterRule *)cur->data;
1052 GSList *cur_cond;
1053 GSList *cur_action;
1054 gchar match_type[64];
1055 gchar nstr[16];
1056
1057 fputs(" <rule name=\"", pfile->fp);
1058 xml_file_put_escape_str(pfile->fp, rule->name);
1059 fprintf(pfile->fp, "\" timing=\"%s\"",
1060 rule->timing == FLT_TIMING_ON_RECEIVE ? "receive" :
1061 rule->timing == FLT_TIMING_MANUAL ? "manual" : "any");
1062 fprintf(pfile->fp, " enabled=\"%s\">\n",
1063 rule->enabled ? "true" : "false");
1064
1065 fprintf(pfile->fp, " <condition-list bool=\"%s\">\n",
1066 rule->bool_op == FLT_OR ? "or" : "and");
1067
1068 for (cur_cond = rule->cond_list; cur_cond != NULL;
1069 cur_cond = cur_cond->next) {
1070 FilterCond *cond = (FilterCond *)cur_cond->data;
1071 XMLNode *node = NULL;
1072
1073 switch (cond->match_type) {
1074 case FLT_CONTAIN:
1075 strncpy2(match_type,
1076 FLT_IS_NOT_MATCH(cond->match_flag)
1077 ? "not-contain" : "contains",
1078 sizeof(match_type));
1079 break;
1080 case FLT_EQUAL:
1081 strncpy2(match_type,
1082 FLT_IS_NOT_MATCH(cond->match_flag)
1083 ? "is-not" : "is", sizeof(match_type));
1084 break;
1085 case FLT_REGEX:
1086 strncpy2(match_type,
1087 FLT_IS_NOT_MATCH(cond->match_flag)
1088 ? "not-regex" : "regex",
1089 sizeof(match_type));
1090 break;
1091 case FLT_IN_ADDRESSBOOK:
1092 strncpy2(match_type,
1093 FLT_IS_NOT_MATCH(cond->match_flag)
1094 ? "not-in-addressbook" : "in-addressbook",
1095 sizeof(match_type));
1096 break;
1097 default:
1098 match_type[0] = '\0';
1099 break;
1100 }
1101
1102 switch (cond->type) {
1103 case FLT_COND_HEADER:
1104 NODE_NEW("match-header", cond->str_value);
1105 ADD_ATTR("type", match_type);
1106 ADD_ATTR("name", cond->header_name);
1107 if (FLT_IS_CASE_SENS(cond->match_flag))
1108 ADD_ATTR("case", "true");
1109 break;
1110 case FLT_COND_ANY_HEADER:
1111 NODE_NEW("match-any-header", cond->str_value);
1112 ADD_ATTR("type", match_type);
1113 if (FLT_IS_CASE_SENS(cond->match_flag))
1114 ADD_ATTR("case", "true");
1115 break;
1116 case FLT_COND_TO_OR_CC:
1117 NODE_NEW("match-to-or-cc", cond->str_value);
1118 ADD_ATTR("type", match_type);
1119 if (FLT_IS_CASE_SENS(cond->match_flag))
1120 ADD_ATTR("case", "true");
1121 break;
1122 case FLT_COND_BODY:
1123 NODE_NEW("match-body-text", cond->str_value);
1124 ADD_ATTR("type", match_type);
1125 if (FLT_IS_CASE_SENS(cond->match_flag))
1126 ADD_ATTR("case", "true");
1127 break;
1128 case FLT_COND_CMD_TEST:
1129 NODE_NEW("command-test", cond->str_value);
1130 break;
1131 case FLT_COND_SIZE_GREATER:
1132 NODE_NEW("size", itos_buf(nstr, cond->int_value));
1133 ADD_ATTR("type",
1134 FLT_IS_NOT_MATCH(cond->match_flag)
1135 ? "lt" : "gt");
1136 break;
1137 case FLT_COND_AGE_GREATER:
1138 NODE_NEW("age", itos_buf(nstr, cond->int_value));
1139 ADD_ATTR("type",
1140 FLT_IS_NOT_MATCH(cond->match_flag)
1141 ? "lt" : "gt");
1142 break;
1143 case FLT_COND_UNREAD:
1144 NODE_NEW("unread", NULL);
1145 ADD_ATTR("type",
1146 FLT_IS_NOT_MATCH(cond->match_flag)
1147 ? "is-not" : "is");
1148 break;
1149 case FLT_COND_MARK:
1150 NODE_NEW("mark", NULL);
1151 ADD_ATTR("type",
1152 FLT_IS_NOT_MATCH(cond->match_flag)
1153 ? "is-not" : "is");
1154 break;
1155 case FLT_COND_COLOR_LABEL:
1156 NODE_NEW("color-label", NULL);
1157 ADD_ATTR("type",
1158 FLT_IS_NOT_MATCH(cond->match_flag)
1159 ? "is-not" : "is");
1160 break;
1161 case FLT_COND_MIME:
1162 NODE_NEW("mime", NULL);
1163 ADD_ATTR("type",
1164 FLT_IS_NOT_MATCH(cond->match_flag)
1165 ? "is-not" : "is");
1166 break;
1167 case FLT_COND_ACCOUNT:
1168 NODE_NEW("account-id", itos_buf(nstr, cond->int_value));
1169 break;
1170 default:
1171 break;
1172 }
1173
1174 if (node) {
1175 fputs(" ", pfile->fp);
1176 xml_file_put_node(pfile->fp, node);
1177 xml_free_node(node);
1178 }
1179 }
1180
1181 if (rule->target_folder) {
1182 XMLNode *node;
1183
1184 NODE_NEW("target-folder", rule->target_folder);
1185 ADD_ATTR("recursive", rule->recursive
1186 ? "true" : "false");
1187 fputs(" ", pfile->fp);
1188 xml_file_put_node(pfile->fp, node);
1189 xml_free_node(node);
1190 }
1191
1192 fputs(" </condition-list>\n", pfile->fp);
1193
1194 fputs(" <action-list>\n", pfile->fp);
1195
1196 for (cur_action = rule->action_list; cur_action != NULL;
1197 cur_action = cur_action->next) {
1198 FilterAction *action = (FilterAction *)cur_action->data;
1199 XMLNode *node = NULL;
1200
1201 switch (action->type) {
1202 case FLT_ACTION_MOVE:
1203 NODE_NEW("move", action->str_value);
1204 break;
1205 case FLT_ACTION_COPY:
1206 NODE_NEW("copy", action->str_value);
1207 break;
1208 case FLT_ACTION_NOT_RECEIVE:
1209 NODE_NEW("not-receive", NULL);
1210 break;
1211 case FLT_ACTION_DELETE:
1212 NODE_NEW("delete", NULL);
1213 break;
1214 case FLT_ACTION_EXEC:
1215 NODE_NEW("exec", action->str_value);
1216 break;
1217 case FLT_ACTION_EXEC_ASYNC:
1218 NODE_NEW("exec-async", action->str_value);
1219 break;
1220 case FLT_ACTION_MARK:
1221 NODE_NEW("mark", NULL);
1222 break;
1223 case FLT_ACTION_COLOR_LABEL:
1224 NODE_NEW("color-label", action->str_value);
1225 break;
1226 case FLT_ACTION_MARK_READ:
1227 NODE_NEW("mark-as-read", NULL);
1228 break;
1229 case FLT_ACTION_FORWARD:
1230 NODE_NEW("forward", action->str_value);
1231 break;
1232 case FLT_ACTION_FORWARD_AS_ATTACHMENT:
1233 NODE_NEW("forward-as-attachment",
1234 action->str_value);
1235 break;
1236 case FLT_ACTION_REDIRECT:
1237 NODE_NEW("redirect", action->str_value);
1238 break;
1239 case FLT_ACTION_STOP_EVAL:
1240 NODE_NEW("stop-eval", NULL);
1241 break;
1242 default:
1243 break;
1244 }
1245
1246 if (node) {
1247 fputs(" ", pfile->fp);
1248 xml_file_put_node(pfile->fp, node);
1249 xml_free_node(node);
1250 }
1251 }
1252
1253 fputs(" </action-list>\n", pfile->fp);
1254
1255 fputs(" </rule>\n", pfile->fp);
1256 }
1257
1258 fputs("</filter>\n", pfile->fp);
1259
1260 if (prefs_file_close(pfile) < 0) {
1261 g_warning("failed to write filter configuration to file: %s\n",
1262 file);
1263 return;
1264 }
1265}
1266
1267void filter_write_config(void)
1268{
1269 gchar *rcpath;
1270
1271 debug_print("Writing filter configuration...\n");
1272
1273 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST,
1274 NULL);
1275 filter_write_file(prefs_common.fltlist, rcpath);
1276 g_free(rcpath);
1277}
1278
1279#undef NODE_NEW
1280#undef ADD_ATTR
1281
1282gchar *filter_get_str(FilterRule *rule)
1283{
1284 gchar *str;
1285 FilterCond *cond1, *cond2;
1286 FilterAction *action = NULL;
1287 GSList *cur;
1288 gint flag1 = 0, flag2 = 0;
1289
1290 cond1 = (FilterCond *)rule->cond_list->data;
1291 if (rule->cond_list->next)
1292 cond2 = (FilterCond *)rule->cond_list->next->data;
1293 else
1294 cond2 = NULL;
1295
1296 /* new -> old flag conversion */
1297 switch (cond1->match_type) {
1298 case FLT_CONTAIN:
1299 case FLT_EQUAL:
1300 flag1 = FLT_IS_NOT_MATCH(cond1->match_flag) ? 0 : FLT_O_CONTAIN;
1301 if (FLT_IS_CASE_SENS(cond1->match_flag))
1302 flag1 |= FLT_O_CASE_SENS;
1303 break;
1304 case FLT_REGEX:
1305 flag1 = FLT_O_REGEX; break;
1306 default:
1307 break;
1308 }
1309 if (cond2) {
1310 switch (cond2->match_type) {
1311 case FLT_CONTAIN:
1312 case FLT_EQUAL:
1313 flag2 = FLT_IS_NOT_MATCH(cond2->match_flag) ? 0 : FLT_O_CONTAIN;
1314 if (FLT_IS_CASE_SENS(cond2->match_flag))
1315 flag2 |= FLT_O_CASE_SENS;
1316 break;
1317 case FLT_REGEX:
1318 flag2 = FLT_O_REGEX; break;
1319 default:
1320 break;
1321 }
1322 } else
1323 flag2 = FLT_O_CONTAIN;
1324
1325 for (cur = rule->action_list; cur != NULL; cur = cur->next) {
1326 action = (FilterAction *)cur->data;
1327 if (action->type == FLT_ACTION_MOVE ||
1328 action->type == FLT_ACTION_NOT_RECEIVE ||
1329 action->type == FLT_ACTION_DELETE)
1330 break;
1331 }
1332
1333 str = g_strdup_printf
1334 ("%s:%s:%c:%s:%s:%s:%d:%d:%c",
1335 cond1->header_name, cond1->str_value ? cond1->str_value : "",
1336 (cond2 && cond2->header_name) ?
1337 (rule->bool_op == FLT_AND ? '&' : '|') : ' ',
1338 (cond2 && cond2->header_name) ? cond2->header_name : "",
1339 (cond2 && cond2->str_value) ? cond2->str_value : "",
1340 (action && action->str_value) ? action->str_value : "",
1341 flag1, flag2,
1342 (action && action->type == FLT_ACTION_MOVE) ? 'm' :
1343 (action && action->type == FLT_ACTION_NOT_RECEIVE) ? 'n' :
1344 (action && action->type == FLT_ACTION_DELETE) ? 'd' : ' ');
1345
1346 return str;
1347}
1348
1349#define PARSE_ONE_PARAM(p, srcp) \
1350{ \
1351 p = strchr(srcp, '\t'); \
1352 if (!p) return NULL; \
1353 else \
1354 *p++ = '\0'; \
1355}
1356
1357FilterRule *filter_read_str(const gchar *str)
1358{
1359 FilterRule *rule;
1360 FilterBoolOp bool_op;
1361 gint flag;
1362 FilterCond *cond;
1363 FilterMatchType match_type;
1364 FilterMatchFlag match_flag;
1365 FilterAction *action;
1366 GSList *cond_list = NULL;
1367 GSList *action_list = NULL;
1368 gchar *tmp;
1369 gchar *rule_name;
1370 gchar *name1, *body1, *op, *name2, *body2, *dest;
1371 gchar *flag1 = NULL, *flag2 = NULL, *action1 = NULL;
1372
1373 Xstrdup_a(tmp, str, return NULL);
1374
1375 name1 = tmp;
1376 PARSE_ONE_PARAM(body1, name1);
1377 PARSE_ONE_PARAM(op, body1);
1378 PARSE_ONE_PARAM(name2, op);
1379 PARSE_ONE_PARAM(body2, name2);
1380 PARSE_ONE_PARAM(dest, body2);
1381 if (strchr(dest, '\t')) {
1382 gchar *p;
1383
1384 PARSE_ONE_PARAM(flag1, dest);
1385 PARSE_ONE_PARAM(flag2, flag1);
1386 PARSE_ONE_PARAM(action1, flag2);
1387 if ((p = strchr(action1, '\t'))) *p = '\0';
1388 }
1389
1390 bool_op = (*op == '&') ? FLT_AND : FLT_OR;
1391
1392 if (*name1) {
1393 if (flag1)
1394 flag = (FilterOldFlag)strtoul(flag1, NULL, 10);
1395 else
1396 flag = FLT_O_CONTAIN;
1397 match_type = FLT_CONTAIN;
1398 match_flag = 0;
1399 if (flag & FLT_O_REGEX)
1400 match_type = FLT_REGEX;
1401 else if (!(flag & FLT_O_CONTAIN))
1402 match_flag = FLT_NOT_MATCH;
1403 if (flag & FLT_O_CASE_SENS)
1404 match_flag |= FLT_CASE_SENS;
1405 cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag,
1406 name1, body1);
1407 cond_list = g_slist_append(cond_list, cond);
1408 }
1409 if (*name2) {
1410 if (flag2)
1411 flag = (FilterOldFlag)strtoul(flag2, NULL, 10);
1412 else
1413 flag = FLT_O_CONTAIN;
1414 match_type = FLT_CONTAIN;
1415 match_flag = 0;
1416 if (flag & FLT_O_REGEX)
1417 match_type = FLT_REGEX;
1418 else if (!(flag & FLT_O_CONTAIN))
1419 match_flag = FLT_NOT_MATCH;
1420 if (flag & FLT_O_CASE_SENS)
1421 match_flag |= FLT_CASE_SENS;
1422 cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag,
1423 name2, body2);
1424 cond_list = g_slist_append(cond_list, cond);
1425 }
1426
1427 action = filter_action_new(FLT_ACTION_MOVE,
1428 *dest ? g_strdup(dest) : NULL);
1429 if (action1) {
1430 switch (*action1) {
1431 case 'm': action->type = FLT_ACTION_MOVE; break;
1432 case 'n': action->type = FLT_ACTION_NOT_RECEIVE; break;
1433 case 'd': action->type = FLT_ACTION_DELETE; break;
1434 default: g_warning("Invalid action: `%c'\n", *action1);
1435 }
1436 }
1437 action_list = g_slist_append(action_list, action);
1438
1439 Xstrdup_a(rule_name, str, return NULL);
1440 subst_char(rule_name, '\t', ':');
1441
1442 rule = filter_rule_new(rule_name, bool_op, cond_list, action_list);
1443
1444 return rule;
1445}
1446
1447void filter_set_addressbook_func(FilterInAddressBookFunc func)
1448{
1449 default_addrbook_func = func;
1450}
1451
1452FilterInAddressBookFunc filter_get_addressbook_func(void)
1453{
1454 return default_addrbook_func;
1455}
1456
1457FilterRule *filter_rule_new(const gchar *name, FilterBoolOp bool_op,
1458 GSList *cond_list, GSList *action_list)
1459{
1460 FilterRule *rule;
1461
1462 rule = g_new0(FilterRule, 1);
1463 rule->name = g_strdup(name);
1464 rule->bool_op = bool_op;
1465 rule->cond_list = cond_list;
1466 rule->action_list = action_list;
1467 rule->timing = FLT_TIMING_ANY;
1468 rule->enabled = TRUE;
1469
1470 return rule;
1471}
1472
1473FilterCond *filter_cond_new(FilterCondType type,
1474 FilterMatchType match_type,
1475 FilterMatchFlag match_flag,
1476 const gchar *header, const gchar *value)
1477{
1478 FilterCond *cond;
1479
1480 cond = g_new0(FilterCond, 1);
1481 cond->type = type;
1482 cond->match_type = match_type;
1483 cond->match_flag = match_flag;
1484
1485 if (type == FLT_COND_HEADER)
1486 cond->header_name =
1487 (header && *header) ? g_strdup(header) : NULL;
1488 else
1489 cond->header_name = NULL;
1490
1491 cond->str_value = (value && *value) ? g_strdup(value) : NULL;
1492 if (type == FLT_COND_SIZE_GREATER || type == FLT_COND_AGE_GREATER ||
1493 type == FLT_COND_ACCOUNT)
1494 cond->int_value = atoi(value);
1495 else
1496 cond->int_value = 0;
1497
1498 if (match_type == FLT_REGEX)
1499 cond->match_func = strmatch_regex;
1500 else if (match_type == FLT_EQUAL) {
1501 if (FLT_IS_CASE_SENS(match_flag))
1502 cond->match_func = str_find_equal;
1503 else
1504 cond->match_func = str_case_find_equal;
1505 } else if (match_type == FLT_IN_ADDRESSBOOK) {
1506 cond->match_func = str_case_find_equal;
1507 } else {
1508 if (FLT_IS_CASE_SENS(match_flag))
1509 cond->match_func = str_find;
1510 else
1511 cond->match_func = str_case_find;
1512 }
1513
1514 return cond;
1515}
1516
1517FilterAction *filter_action_new(FilterActionType type, const gchar *str)
1518{
1519 FilterAction *action;
1520
1521 action = g_new0(FilterAction, 1);
1522 action->type = type;
1523
1524 action->str_value = (str && *str) ? g_strdup(str) : NULL;
1525 if (type == FLT_ACTION_COLOR_LABEL && str)
1526 action->int_value = atoi(str);
1527 else
1528 action->int_value = 0;
1529
1530 return action;
1531}
1532
1533FilterInfo *filter_info_new(void)
1534{
1535 FilterInfo *fltinfo;
1536
1537 fltinfo = g_new0(FilterInfo, 1);
1538 fltinfo->dest_list = NULL;
1539 fltinfo->move_dest = NULL;
1540 fltinfo->drop_done = FALSE;
1541 fltinfo->error = FLT_ERROR_OK;
1542 fltinfo->last_exec_exit_status = 0;
1543
1544 return fltinfo;
1545}
1546
1547FilterRule *filter_junk_rule_create(PrefsAccount *account,
1548 FolderItem *default_junk,
1549 gboolean is_manual)
1550{
1551 FilterRule *rule;
1552 FilterCond *cond;
1553 FilterAction *action;
1554 GSList *cond_list = NULL, *action_list = NULL;
1555 gchar *junk_id = NULL;
1556 FolderItem *item = NULL;
1557
1558 if (!prefs_common.junk_classify_cmd)
1559 return NULL;
1560
1561 if (prefs_common.junk_folder)
1562 item = folder_find_item_from_identifier(prefs_common.junk_folder);
1563
1564 if (!item && account) {
1565 Folder *folder = NULL;
1566 GList *list;
1567
1568 /* find most suitable Junk folder for account */
1569
1570 if (account->inbox && *account->inbox == '#') {
1571 FolderItem *inbox;
1572 inbox = folder_find_item_from_identifier(account->inbox);
1573 if (inbox) {
1574 folder = inbox->folder;
1575 if (folder)
1576 item = folder_get_junk(folder);
1577 }
1578 }
1579 if (!item) {
1580 folder = FOLDER(account->folder);
1581 if (folder)
1582 item = folder_get_junk(folder);
1583 }
1584 if (!item) {
1585 for (list = folder_get_list(); list != NULL; list = list->next) {
1586 folder = FOLDER(list->data);
1587 if (FOLDER_IS_LOCAL(folder)) {
1588 if (folder->account == account)
1589 item = folder_get_junk(folder);
1590 if (!item && folder->node) {
1591 item = FOLDER_ITEM(folder->node->data);
1592 if (item && item->account == account && item->folder)
1593 item = folder_get_junk(item->folder);
1594 else
1595 item = NULL;
1596 }
1597 }
1598 if (item)
1599 break;
1600 }
1601 }
1602 }
1603
1604 if (!item)
1605 item = default_junk;
1606 if (!item)
1607 item = folder_get_default_junk();
1608 if (!item)
1609 return NULL;
1610 junk_id = folder_item_get_identifier(item);
1611 if (!junk_id)
1612 return NULL;
1613
1614 debug_print("filter_junk_rule_create: junk folder: %s\n",
1615 junk_id);
1616
1617 if (prefs_common.nofilter_junk_sender_in_book) {
1618 cond = filter_cond_new(FLT_COND_HEADER, FLT_IN_ADDRESSBOOK,
1619 FLT_NOT_MATCH, "From", NULL);
1620 cond_list = g_slist_append(cond_list, cond);
1621 }
1622
1623 cond = filter_cond_new(FLT_COND_CMD_TEST, 0, 0, NULL,
1624 prefs_common.junk_classify_cmd);
1625 cond_list = g_slist_append(cond_list, cond);
1626
1627 if (prefs_common.delete_junk_on_recv && !is_manual) {
1628 action = filter_action_new(FLT_ACTION_COPY, junk_id);
1629 action_list = g_slist_append(NULL, action);
1630 action = filter_action_new(FLT_ACTION_DELETE, NULL);
1631 action_list = g_slist_append(action_list, action);
1632 } else {
1633 action = filter_action_new(FLT_ACTION_MOVE, junk_id);
1634 action_list = g_slist_append(NULL, action);
1635 }
1636
1637 if (prefs_common.mark_junk_as_read) {
1638 action = filter_action_new(FLT_ACTION_MARK_READ, NULL);
1639 action_list = g_slist_append(action_list, action);
1640 }
1641
1642 if (is_manual)
1643 rule = filter_rule_new(_("Junk mail filter (manual)"), FLT_AND,
1644 cond_list, action_list);
1645 else
1646 rule = filter_rule_new(_("Junk mail filter"), FLT_AND,
1647 cond_list, action_list);
1648
1649 g_free(junk_id);
1650
1651 return rule;
1652}
1653
1654void filter_rule_rename_dest_path(FilterRule *rule, const gchar *old_path,
1655 const gchar *new_path)
1656{
1657 FilterAction *action;
1658 GSList *cur;
1659 gchar *base;
1660 gchar *dest_path;
1661 gint oldpathlen;
1662
1663 oldpathlen = strlen(old_path);
1664
1665 for (cur = rule->action_list; cur != NULL; cur = cur->next) {
1666 action = (FilterAction *)cur->data;
1667
1668 if (action->type != FLT_ACTION_MOVE &&
1669 action->type != FLT_ACTION_COPY)
1670 continue;
1671
1672 if (action->str_value &&
1673 !strncmp(old_path, action->str_value, oldpathlen)) {
1674 base = action->str_value + oldpathlen;
1675 if (*base != '/' && *base != '\0')
1676 continue;
1677 while (*base == '/') base++;
1678 if (*base == '\0')
1679 dest_path = g_strdup(new_path);
1680 else
1681 dest_path = g_strconcat(new_path, "/", base,
1682 NULL);
1683 debug_print("filter_rule_rename_dest_path(): "
1684 "renaming %s -> %s\n",
1685 action->str_value, dest_path);
1686 g_free(action->str_value);
1687 action->str_value = dest_path;
1688 }
1689 }
1690}
1691
1692void filter_rule_delete_action_by_dest_path(FilterRule *rule, const gchar *path)
1693{
1694 FilterAction *action;
1695 GSList *cur;
1696 GSList *next;
1697 gint pathlen;
1698
1699 pathlen = strlen(path);
1700
1701 for (cur = rule->action_list; cur != NULL; cur = next) {
1702 action = (FilterAction *)cur->data;
1703 next = cur->next;
1704
1705 if (action->type != FLT_ACTION_MOVE &&
1706 action->type != FLT_ACTION_COPY)
1707 continue;
1708
1709 if (action->str_value &&
1710 !strncmp(path, action->str_value, pathlen) &&
1711 (action->str_value[pathlen] == '/' ||
1712 action->str_value[pathlen] == '\0')) {
1713 debug_print("filter_rule_delete_action_by_dest_path(): "
1714 "deleting %s\n", action->str_value);
1715 rule->action_list = g_slist_remove
1716 (rule->action_list, action);
1717 filter_action_free(action);
1718 }
1719 }
1720}
1721
1722void filter_list_rename_path(const gchar *old_path, const gchar *new_path)
1723{
1724 GSList *cur;
1725
1726 g_return_if_fail(old_path != NULL);
1727 g_return_if_fail(new_path != NULL);
1728
1729 for (cur = prefs_common.fltlist; cur != NULL; cur = cur->next) {
1730 FilterRule *rule = (FilterRule *)cur->data;
1731 filter_rule_rename_dest_path(rule, old_path, new_path);
1732 }
1733
1734 filter_write_config();
1735}
1736
1737void filter_list_delete_path(const gchar *path)
1738{
1739 GSList *cur;
1740 GSList *next;
1741
1742 g_return_if_fail(path != NULL);
1743
1744 for (cur = prefs_common.fltlist; cur != NULL; cur = next) {
1745 FilterRule *rule = (FilterRule *)cur->data;
1746 next = cur->next;
1747
1748 filter_rule_delete_action_by_dest_path(rule, path);
1749 if (!rule->action_list) {
1750 prefs_common.fltlist =
1751 g_slist_remove(prefs_common.fltlist, rule);
1752 filter_rule_free(rule);
1753 }
1754 }
1755
1756 filter_write_config();
1757}
1758
1759void filter_rule_match_type_str_to_enum(const gchar *match_type,
1760 FilterMatchType *type,
1761 FilterMatchFlag *flag)
1762{
1763 g_return_if_fail(match_type != NULL);
1764
1765 *type = FLT_CONTAIN;
1766 *flag = 0;
1767
1768 if (!strcmp(match_type, "contains")) {
1769 *type = FLT_CONTAIN;
1770 } else if (!strcmp(match_type, "not-contain")) {
1771 *type = FLT_CONTAIN;
1772 *flag = FLT_NOT_MATCH;
1773 } else if (!strcmp(match_type, "is")) {
1774 *type = FLT_EQUAL;
1775 } else if (!strcmp(match_type, "is-not")) {
1776 *type = FLT_EQUAL;
1777 *flag = FLT_NOT_MATCH;
1778 } else if (!strcmp(match_type, "regex")) {
1779 *type = FLT_REGEX;
1780 } else if (!strcmp(match_type, "not-regex")) {
1781 *type = FLT_REGEX;
1782 *flag = FLT_NOT_MATCH;
1783 } else if (!strcmp(match_type, "in-addressbook")) {
1784 *type = FLT_IN_ADDRESSBOOK;
1785 } else if (!strcmp(match_type, "not-in-addressbook")) {
1786 *type = FLT_IN_ADDRESSBOOK;
1787 *flag = FLT_NOT_MATCH;
1788 } else if (!strcmp(match_type, "gt")) {
1789 } else if (!strcmp(match_type, "lt")) {
1790 *flag = FLT_NOT_MATCH;
1791 }
1792}
1793
1794void filter_get_keyword_from_msg(MsgInfo *msginfo, gchar **header, gchar **key,
1795 FilterCreateType type)
1796{
1797 static HeaderEntry hentry[] = {{"List-Id:", NULL, TRUE},
1798 {"X-ML-Name:", NULL, TRUE},
1799 {"X-List:", NULL, TRUE},
1800 {"X-Mailing-list:", NULL, TRUE},
1801 {"X-Sequence:", NULL, TRUE},
1802 {NULL, NULL, FALSE}};
1803 enum
1804 {
1805 H_LIST_ID = 0,
1806 H_X_ML_NAME = 1,
1807 H_X_LIST = 2,
1808 H_X_MAILING_LIST = 3,
1809 H_X_SEQUENCE = 4
1810 };
1811
1812 FILE *fp;
1813
1814 g_return_if_fail(msginfo != NULL);
1815 g_return_if_fail(header != NULL);
1816 g_return_if_fail(key != NULL);
1817
1818 *header = NULL;
1819 *key = NULL;
1820
1821 switch (type) {
1822 case FLT_BY_NONE:
1823 return;
1824 case FLT_BY_AUTO:
1825 if ((fp = procmsg_open_message(msginfo)) == NULL)
1826 return;
1827 procheader_get_header_fields(fp, hentry);
1828 fclose(fp);
1829
1830#define SET_FILTER_KEY(hstr, idx) \
1831{ \
1832 *header = g_strdup(hstr); \
1833 *key = hentry[idx].body; \
1834 hentry[idx].body = NULL; \
1835}
1836
1837 if (hentry[H_LIST_ID].body != NULL) {
1838 SET_FILTER_KEY("List-Id", H_LIST_ID);
1839 extract_list_id_str(*key);
1840 } else if (hentry[H_X_ML_NAME].body != NULL) {
1841 SET_FILTER_KEY("X-ML-Name", H_X_ML_NAME);
1842 } else if (hentry[H_X_LIST].body != NULL) {
1843 SET_FILTER_KEY("X-List", H_X_LIST);
1844 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
1845 SET_FILTER_KEY("X-Mailing-list", H_X_MAILING_LIST);
1846 } else if (hentry[H_X_SEQUENCE].body != NULL) {
1847 gchar *p;
1848
1849 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
1850 p = *key;
1851 while (*p != '\0') {
1852 while (*p != '\0' && !g_ascii_isspace(*p)) p++;
1853 while (g_ascii_isspace(*p)) p++;
1854 if (g_ascii_isdigit(*p)) {
1855 *p = '\0';
1856 break;
1857 }
1858 }
1859 g_strstrip(*key);
1860 } else if (msginfo->subject) {
1861 *header = g_strdup("Subject");
1862 *key = g_strdup(msginfo->subject);
1863 }
1864
1865#undef SET_FILTER_KEY
1866
1867 g_free(hentry[H_LIST_ID].body);
1868 hentry[H_LIST_ID].body = NULL;
1869 g_free(hentry[H_X_ML_NAME].body);
1870 hentry[H_X_ML_NAME].body = NULL;
1871 g_free(hentry[H_X_LIST].body);
1872 hentry[H_X_LIST].body = NULL;
1873 g_free(hentry[H_X_MAILING_LIST].body);
1874 hentry[H_X_MAILING_LIST].body = NULL;
1875
1876 break;
1877 case FLT_BY_FROM:
1878 *header = g_strdup("From");
1879 *key = g_strdup(msginfo->from);
1880 break;
1881 case FLT_BY_TO:
1882 *header = g_strdup("To");
1883 *key = g_strdup(msginfo->to);
1884 break;
1885 case FLT_BY_SUBJECT:
1886 *header = g_strdup("Subject");
1887 *key = g_strdup(msginfo->subject);
1888 break;
1889 default:
1890 break;
1891 }
1892}
1893
1894void filter_rule_list_free(GSList *fltlist)
1895{
1896 GSList *cur;
1897
1898 for (cur = fltlist; cur != NULL; cur = cur->next)
1899 filter_rule_free((FilterRule *)cur->data);
1900 g_slist_free(fltlist);
1901}
1902
1903void filter_rule_free(FilterRule *rule)
1904{
1905 if (!rule) return;
1906
1907 g_free(rule->name);
1908 g_free(rule->target_folder);
1909
1910 filter_cond_list_free(rule->cond_list);
1911 filter_action_list_free(rule->action_list);
1912
1913 g_free(rule);
1914}
1915
1916void filter_cond_list_free(GSList *cond_list)
1917{
1918 GSList *cur;
1919
1920 for (cur = cond_list; cur != NULL; cur = cur->next)
1921 filter_cond_free((FilterCond *)cur->data);
1922 g_slist_free(cond_list);
1923}
1924
1925void filter_action_list_free(GSList *action_list)
1926{
1927 GSList *cur;
1928
1929 for (cur = action_list; cur != NULL; cur = cur->next)
1930 filter_action_free((FilterAction *)cur->data);
1931 g_slist_free(action_list);
1932}
1933
1934static void filter_cond_free(FilterCond *cond)
1935{
1936 g_free(cond->header_name);
1937 g_free(cond->str_value);
1938 g_free(cond);
1939}
1940
1941static void filter_action_free(FilterAction *action)
1942{
1943 g_free(action->str_value);
1944 g_free(action);
1945}
1946
1947void filter_info_free(FilterInfo *fltinfo)
1948{
1949 g_slist_free(fltinfo->dest_list);
1950 g_free(fltinfo);
1951}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2010 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __FILTER_H__
21#define __FILTER_H__
22
23#include <glib.h>
24
25#include "folder.h"
26#include "procmsg.h"
27#include "utils.h"
28
29typedef struct _FilterCond FilterCond;
30typedef struct _FilterAction FilterAction;
31typedef struct _FilterRule FilterRule;
32typedef struct _FilterInfo FilterInfo;
33
34typedef enum
35{
36 FLT_TIMING_ANY,
37 FLT_TIMING_ON_RECEIVE,
38 FLT_TIMING_MANUAL
39} FilterTiming;
40
41typedef enum
42{
43 FLT_COND_HEADER,
44 FLT_COND_ANY_HEADER,
45 FLT_COND_TO_OR_CC,
46 FLT_COND_BODY,
47 FLT_COND_CMD_TEST,
48 FLT_COND_SIZE_GREATER,
49 FLT_COND_AGE_GREATER,
50 FLT_COND_UNREAD,
51 FLT_COND_MARK,
52 FLT_COND_COLOR_LABEL,
53 FLT_COND_MIME,
54 FLT_COND_ACCOUNT
55} FilterCondType;
56
57typedef enum
58{
59 FLT_CONTAIN,
60 FLT_EQUAL,
61 FLT_REGEX,
62 FLT_IN_ADDRESSBOOK
63} FilterMatchType;
64
65typedef enum
66{
67 FLT_NOT_MATCH = 1 << 0,
68 FLT_CASE_SENS = 1 << 1
69} FilterMatchFlag;
70
71typedef enum
72{
73 FLT_OR,
74 FLT_AND
75} FilterBoolOp;
76
77typedef enum
78{
79 FLT_ACTION_MOVE,
80 FLT_ACTION_COPY,
81 FLT_ACTION_NOT_RECEIVE,
82 FLT_ACTION_DELETE,
83 FLT_ACTION_EXEC,
84 FLT_ACTION_EXEC_ASYNC,
85 FLT_ACTION_MARK,
86 FLT_ACTION_COLOR_LABEL,
87 FLT_ACTION_MARK_READ,
88 FLT_ACTION_FORWARD,
89 FLT_ACTION_FORWARD_AS_ATTACHMENT,
90 FLT_ACTION_REDIRECT,
91 FLT_ACTION_STOP_EVAL,
92 FLT_ACTION_NONE
93} FilterActionType;
94
95typedef enum
96{
97 FLT_BY_NONE,
98 FLT_BY_AUTO,
99 FLT_BY_FROM,
100 FLT_BY_TO,
101 FLT_BY_SUBJECT
102} FilterCreateType;
103
104typedef enum
105{
106 FLT_ERROR_OK,
107 FLT_ERROR_ERROR,
108 FLT_ERROR_EXEC_FAILED
109} FilterErrorValue;
110
111#define FLT_IS_NOT_MATCH(flag) ((flag & FLT_NOT_MATCH) != 0)
112#define FLT_IS_CASE_SENS(flag) ((flag & FLT_CASE_SENS) != 0)
113
114typedef gboolean (*FilterInAddressBookFunc) (const gchar *address);
115
116struct _FilterCond
117{
118 FilterCondType type;
119
120 gchar *header_name;
121
122 gchar *str_value;
123 gint int_value;
124
125 FilterMatchType match_type;
126 FilterMatchFlag match_flag;
127
128 StrFindFunc match_func;
129};
130
131struct _FilterAction
132{
133 FilterActionType type;
134
135 gchar *str_value;
136 gint int_value;
137};
138
139struct _FilterRule
140{
141 gchar *name;
142
143 FilterBoolOp bool_op;
144
145 GSList *cond_list;
146 GSList *action_list;
147
148 FilterTiming timing;
149 gboolean enabled;
150
151 gchar *target_folder;
152 gboolean recursive;
153};
154
155struct _FilterInfo
156{
157 PrefsAccount *account;
158 MsgFlags flags;
159
160 gboolean actions[FLT_ACTION_NONE];
161 GSList *dest_list;
162 FolderItem *move_dest;
163 gboolean drop_done;
164
165 FilterErrorValue error;
166 gint last_exec_exit_status;
167};
168
169gint filter_apply (GSList *fltlist,
170 const gchar *file,
171 FilterInfo *fltinfo);
172gint filter_apply_msginfo (GSList *fltlist,
173 MsgInfo *msginfo,
174 FilterInfo *fltinfo);
175
176gint filter_action_exec (FilterRule *rule,
177 MsgInfo *msginfo,
178 const gchar *file,
179 FilterInfo *fltinfo);
180
181gboolean filter_match_rule (FilterRule *rule,
182 MsgInfo *msginfo,
183 GSList *hlist,
184 FilterInfo *fltinfo);
185
186gboolean filter_rule_requires_full_headers (FilterRule *rule);
187
188/* read / write config */
189GSList *filter_xml_node_to_filter_list (GNode *node);
190GSList *filter_read_file (const gchar *file);
191void filter_read_config (void);
192void filter_write_file (GSList *list,
193 const gchar *file);
194void filter_write_config (void);
195
196/* for old filterrc */
197gchar *filter_get_str (FilterRule *rule);
198FilterRule *filter_read_str (const gchar *str);
199
200void filter_set_addressbook_func (FilterInAddressBookFunc func);
201FilterInAddressBookFunc filter_get_addressbook_func
202 (void);
203
204FilterRule *filter_rule_new (const gchar *name,
205 FilterBoolOp bool_op,
206 GSList *cond_list,
207 GSList *action_list);
208FilterCond *filter_cond_new (FilterCondType type,
209 FilterMatchType match_type,
210 FilterMatchFlag match_flag,
211 const gchar *header,
212 const gchar *value);
213FilterAction *filter_action_new (FilterActionType type,
214 const gchar *str);
215FilterInfo *filter_info_new (void);
216
217FilterRule *filter_junk_rule_create (PrefsAccount *account,
218 FolderItem *default_junk,
219 gboolean is_manual);
220
221void filter_rule_rename_dest_path (FilterRule *rule,
222 const gchar *old_path,
223 const gchar *new_path);
224void filter_rule_delete_action_by_dest_path
225 (FilterRule *rule,
226 const gchar *path);
227
228void filter_list_rename_path (const gchar *old_path,
229 const gchar *new_path);
230void filter_list_delete_path (const gchar *path);
231
232void filter_rule_match_type_str_to_enum (const gchar *type_str,
233 FilterMatchType *type,
234 FilterMatchFlag *flag);
235
236void filter_get_keyword_from_msg (MsgInfo *msginfo,
237 gchar **header,
238 gchar **key,
239 FilterCreateType type);
240
241void filter_rule_list_free (GSList *fltlist);
242void filter_rule_free (FilterRule *rule);
243void filter_cond_list_free (GSList *cond_list);
244void filter_action_list_free (GSList *action_list);
245void filter_info_free (FilterInfo *info);
246
247#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <stdio.h>
29#include <string.h>
30#include <stdlib.h>
31
32#include "folder.h"
33#include "session.h"
34#include "imap.h"
35#include "news.h"
36#include "mh.h"
37#include "virtual.h"
38#include "utils.h"
39#include "xml.h"
40#include "codeconv.h"
41#include "prefs.h"
42#include "account.h"
43#include "prefs_account.h"
44#include "sylmain.h"
45
46typedef struct _FolderPrivData FolderPrivData;
47
48struct _FolderPrivData {
49 Folder *folder;
50 FolderItem *junk;
51
52 FolderUIFunc2 ui_func2;
53 gpointer ui_func2_data;
54
55 gpointer data;
56};
57
58static GList *folder_list = NULL;
59static GList *folder_priv_list = NULL;
60
61static void folder_init (Folder *folder,
62 const gchar *name);
63
64static FolderPrivData *folder_get_priv (Folder *folder);
65
66static gboolean folder_read_folder_func (GNode *node,
67 gpointer data);
68static gchar *folder_get_list_path (void);
69static void folder_write_list_recursive (GNode *node,
70 gpointer data);
71
72
73Folder *folder_new(FolderType type, const gchar *name, const gchar *path)
74{
75 Folder *folder = NULL;
76
77 name = name ? name : path;
78 switch (type) {
79 case F_MH:
80 folder = mh_get_class()->folder_new(name, path);
81 break;
82 case F_IMAP:
83 folder = imap_get_class()->folder_new(name, path);
84 break;
85 case F_NEWS:
86 folder = news_get_class()->folder_new(name, path);
87 break;
88 default:
89 return NULL;
90 }
91
92 return folder;
93}
94
95static void folder_init(Folder *folder, const gchar *name)
96{
97 FolderItem *item;
98
99 g_return_if_fail(folder != NULL);
100
101 folder_set_name(folder, name);
102 folder->account = NULL;
103 folder->inbox = NULL;
104 folder->outbox = NULL;
105 folder->draft = NULL;
106 folder->queue = NULL;
107 folder->trash = NULL;
108 folder->ui_func = NULL;
109 folder->ui_func_data = NULL;
110 item = folder_item_new(name, NULL);
111 item->folder = folder;
112 folder->node = item->node = g_node_new(item);
113 folder->data = NULL;
114}
115
116void folder_local_folder_init(Folder *folder, const gchar *name,
117 const gchar *path)
118{
119 folder_init(folder, name);
120 LOCAL_FOLDER(folder)->rootpath = g_strdup(path);
121}
122
123void folder_remote_folder_init(Folder *folder, const gchar *name,
124 const gchar *path)
125{
126 folder_init(folder, name);
127 REMOTE_FOLDER(folder)->session = NULL;
128 REMOTE_FOLDER(folder)->remove_cache_on_destroy = TRUE;
129}
130
131void folder_destroy(Folder *folder)
132{
133 FolderPrivData *priv;
134
135 g_return_if_fail(folder != NULL);
136 g_return_if_fail(folder->klass->destroy != NULL);
137
138 debug_print("folder_destroy: destroying Folder (%p)\n", folder);
139
140 folder->klass->destroy(folder);
141
142 folder_list = g_list_remove(folder_list, folder);
143
144 folder_tree_destroy(folder);
145
146 priv = folder_get_priv(folder);
147 folder_priv_list = g_list_remove(folder_priv_list, priv);
148 g_free(priv);
149
150 g_free(folder->name);
151 g_free(folder);
152}
153
154void folder_local_folder_destroy(LocalFolder *lfolder)
155{
156 g_return_if_fail(lfolder != NULL);
157
158 g_free(lfolder->rootpath);
159}
160
161void folder_remote_folder_destroy(RemoteFolder *rfolder)
162{
163 g_return_if_fail(rfolder != NULL);
164
165 if (rfolder->session)
166 session_destroy(rfolder->session);
167}
168
169gint folder_remote_folder_destroy_all_sessions(void)
170{
171 GList *list;
172 Folder *folder;
173 RemoteFolder *rfolder;
174
175 for (list = folder_list; list != NULL; list = list->next) {
176 folder = FOLDER(list->data);
177 if (FOLDER_IS_REMOTE(folder)) {
178 rfolder = REMOTE_FOLDER(folder);
179 if (rfolder->session &&
180 !folder_remote_folder_is_session_active(rfolder)) {
181 session_destroy(rfolder->session);
182 rfolder->session = NULL;
183 }
184 }
185 }
186
187 return 0;
188}
189
190gboolean folder_remote_folder_is_session_active(RemoteFolder *rfolder)
191{
192 g_return_val_if_fail(rfolder != NULL, FALSE);
193
194 if (FOLDER_TYPE(rfolder) == F_IMAP)
195 return imap_is_session_active(IMAP_FOLDER(rfolder));
196
197 return FALSE;
198}
199
200gboolean folder_remote_folder_active_session_exist(void)
201{
202 GList *list;
203 Folder *folder;
204 RemoteFolder *rfolder;
205
206 for (list = folder_list; list != NULL; list = list->next) {
207 folder = FOLDER(list->data);
208 if (FOLDER_IS_REMOTE(folder)) {
209 rfolder = REMOTE_FOLDER(folder);
210 if (folder_remote_folder_is_session_active(rfolder))
211 return TRUE;
212 }
213 }
214
215 return FALSE;
216}
217
218gint folder_scan_tree(Folder *folder)
219{
220 g_return_val_if_fail(folder != NULL, -1);
221 g_return_val_if_fail(folder->klass->scan_tree != NULL, -1);
222
223 return folder->klass->scan_tree(folder);
224}
225
226gint folder_create_tree(Folder *folder)
227{
228 g_return_val_if_fail(folder != NULL, -1);
229 g_return_val_if_fail(folder->klass->create_tree != NULL, -1);
230
231 return folder->klass->create_tree(folder);
232}
233
234FolderItem *folder_item_new(const gchar *name, const gchar *path)
235{
236 FolderItem *item;
237
238 item = g_new0(FolderItem, 1);
239
240 item->stype = F_NORMAL;
241 item->name = g_strdup(name);
242 item->path = g_strdup(path);
243 item->mtime = 0;
244 item->new = 0;
245 item->unread = 0;
246 item->total = 0;
247 item->unmarked_num = 0;
248 item->last_num = -1;
249 item->no_sub = FALSE;
250 item->no_select = FALSE;
251 item->collapsed = FALSE;
252 item->threaded = TRUE;
253 item->opened = FALSE;
254 item->updated = FALSE;
255 item->cache_dirty = FALSE;
256 item->mark_dirty = FALSE;
257 item->node = NULL;
258 item->parent = NULL;
259 item->folder = NULL;
260 item->account = NULL;
261 item->ac_apply_sub = FALSE;
262 item->auto_to = NULL;
263 item->use_auto_to_on_reply = FALSE;
264 item->auto_cc = NULL;
265 item->auto_bcc = NULL;
266 item->auto_replyto = NULL;
267 item->mark_queue = NULL;
268 item->last_selected = 0;
269 item->qsearch_cond_type = 0;
270 item->data = NULL;
271
272 return item;
273}
274
275void folder_item_append(FolderItem *parent, FolderItem *item)
276{
277 g_return_if_fail(parent != NULL);
278 g_return_if_fail(parent->folder != NULL);
279 g_return_if_fail(parent->node != NULL);
280 g_return_if_fail(item != NULL);
281
282 item->parent = parent;
283 item->folder = parent->folder;
284 item->node = g_node_append_data(parent->node, item);
285}
286
287FolderItem *folder_item_copy(FolderItem *item)
288{
289 FolderItem *new_item;
290
291 new_item = g_new0(FolderItem, 1);
292
293 new_item->stype = item->stype;
294 new_item->name = g_strdup(item->name);
295 new_item->path = g_strdup(item->path);
296 new_item->mtime = item->mtime;
297 new_item->new = item->new;
298 new_item->unread = item->unread;
299 new_item->total = item->total;
300 new_item->unmarked_num = item->unmarked_num;
301 new_item->last_num = item->last_num;
302 new_item->no_sub = item->no_sub;
303 new_item->no_select = item->no_select;
304 new_item->collapsed = item->collapsed;
305 new_item->threaded = item->threaded;
306 new_item->opened = item->opened;
307 new_item->updated = item->updated;
308 new_item->cache_dirty = item->cache_dirty;
309 new_item->mark_dirty = item->mark_dirty;
310 new_item->node = item->node;
311 new_item->parent = item->parent;
312 new_item->folder = item->folder;
313 new_item->account = item->account;
314 new_item->ac_apply_sub = item->ac_apply_sub;
315 new_item->auto_to = g_strdup(item->auto_to);
316 new_item->use_auto_to_on_reply = item->use_auto_to_on_reply;
317 new_item->auto_cc = g_strdup(item->auto_cc);
318 new_item->auto_bcc = g_strdup(item->auto_bcc);
319 new_item->auto_replyto = g_strdup(item->auto_replyto);
320 new_item->mark_queue = item->mark_queue;
321 new_item->last_selected = item->last_selected;
322 new_item->qsearch_cond_type = item->qsearch_cond_type;
323 new_item->data = item->data;
324
325 return new_item;
326}
327
328static gboolean folder_item_remove_func(GNode *node, gpointer data)
329{
330 FolderItem *item = FOLDER_ITEM(node->data);
331
332 folder_item_destroy(item);
333 return FALSE;
334}
335
336void folder_item_remove(FolderItem *item)
337{
338 GNode *node;
339
340 g_return_if_fail(item != NULL);
341 g_return_if_fail(item->folder != NULL);
342 g_return_if_fail(item->node != NULL);
343
344 node = item->node;
345
346 if (item->folder->node == node)
347 item->folder->node = NULL;
348
349 g_node_traverse(node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
350 folder_item_remove_func, NULL);
351 g_node_destroy(node);
352}
353
354void folder_item_remove_children(FolderItem *item)
355{
356 GNode *node, *next;
357
358 g_return_if_fail(item != NULL);
359 g_return_if_fail(item->folder != NULL);
360 g_return_if_fail(item->node != NULL);
361
362 node = item->node->children;
363 while (node != NULL) {
364 next = node->next;
365 folder_item_remove(FOLDER_ITEM(node->data));
366 node = next;
367 }
368}
369
370void folder_item_destroy(FolderItem *item)
371{
372 Folder *folder;
373
374 g_return_if_fail(item != NULL);
375
376 folder = item->folder;
377 if (folder) {
378 if (folder->inbox == item)
379 folder->inbox = NULL;
380 else if (folder->outbox == item)
381 folder->outbox = NULL;
382 else if (folder->draft == item)
383 folder->draft = NULL;
384 else if (folder->queue == item)
385 folder->queue = NULL;
386 else if (folder->trash == item)
387 folder->trash = NULL;
388 else if (folder_get_junk(folder) == item)
389 folder_set_junk(folder, NULL);
390 }
391
392 g_free(item->name);
393 g_free(item->path);
394 g_free(item->auto_to);
395 g_free(item->auto_cc);
396 g_free(item->auto_bcc);
397 g_free(item->auto_replyto);
398 g_free(item);
399}
400
401gint folder_item_compare(FolderItem *item_a, FolderItem *item_b)
402{
403 gint ret;
404 gchar *str_a, *str_b;
405
406 if (!item_a || !item_b)
407 return 0;
408 if (!item_a->parent || !item_b->parent)
409 return 0;
410 if (!item_a->name || !item_b->name)
411 return 0;
412
413 /* if both a and b are special folders, sort them according to
414 * their types (which is in-order). Note that this assumes that
415 * there are no multiple folders of a special type. As a special
416 * case, two virtual folders are compared like normal ones. */
417 if (item_a->stype != F_NORMAL && item_b->stype != F_NORMAL &&
418 (item_a->stype != F_VIRTUAL || item_b->stype != F_VIRTUAL))
419 return item_a->stype - item_b->stype;
420
421 /* if b is normal folder, and a is not, b is smaller (ends up
422 * lower in the list) */
423 if (item_a->stype != F_NORMAL && item_b->stype == F_NORMAL)
424 return item_b->stype - item_a->stype;
425
426 /* if b is special folder, and a is not, b is larger (ends up
427 * higher in the list) */
428 if (item_a->stype == F_NORMAL && item_b->stype != F_NORMAL)
429 return item_b->stype - item_a->stype;
430
431 /* otherwise just compare the folder names */
432 str_a = g_utf8_casefold(item_a->name, -1);
433 str_b = g_utf8_casefold(item_b->name, -1);
434 ret = g_utf8_collate(str_a, str_b);
435 g_free(str_b);
436 g_free(str_a);
437
438 return ret;
439}
440
441void folder_set_ui_func(Folder *folder, FolderUIFunc func, gpointer data)
442{
443 g_return_if_fail(folder != NULL);
444
445 folder->ui_func = func;
446 folder->ui_func_data = data;
447}
448
449void folder_set_ui_func2(Folder *folder, FolderUIFunc2 func, gpointer data)
450{
451 FolderPrivData *priv;
452
453 priv = folder_get_priv(folder);
454 if (priv) {
455 priv->ui_func2 = func;
456 priv->ui_func2_data = data;
457 }
458}
459
460FolderUIFunc2 folder_get_ui_func2(Folder *folder)
461{
462 FolderPrivData *priv;
463
464 priv = folder_get_priv(folder);
465 if (priv)
466 return priv->ui_func2;
467
468 return NULL;
469}
470
471gboolean folder_call_ui_func2(Folder *folder, FolderItem *item, guint count,
472 guint total)
473{
474 FolderPrivData *priv;
475
476 priv = folder_get_priv(folder);
477 if (priv && priv->ui_func2) {
478 return priv->ui_func2(folder, item, count, total,
479 priv->ui_func2_data);
480 }
481
482 return TRUE;
483}
484
485void folder_set_name(Folder *folder, const gchar *name)
486{
487 g_return_if_fail(folder != NULL);
488
489 g_free(folder->name);
490 folder->name = name ? g_strdup(name) : NULL;
491 if (folder->node && folder->node->data) {
492 FolderItem *item = (FolderItem *)folder->node->data;
493
494 g_free(item->name);
495 item->name = name ? g_strdup(name) : NULL;
496 }
497}
498
499void folder_tree_destroy(Folder *folder)
500{
501 g_return_if_fail(folder != NULL);
502
503 if (folder->node)
504 folder_item_remove(FOLDER_ITEM(folder->node->data));
505}
506
507void folder_add(Folder *folder)
508{
509 Folder *cur_folder;
510 GList *cur;
511 gint i;
512 FolderPrivData *priv;
513
514 debug_print("Adding Folder (%p) to folder list\n", folder);
515
516 g_return_if_fail(folder != NULL);
517
518 for (i = 0, cur = folder_list; cur != NULL; cur = cur->next, i++) {
519 cur_folder = FOLDER(cur->data);
520 if (FOLDER_TYPE(folder) == F_MH) {
521 if (FOLDER_TYPE(cur_folder) != F_MH) break;
522 } else if (FOLDER_TYPE(folder) == F_IMAP) {
523 if (FOLDER_TYPE(cur_folder) != F_MH &&
524 FOLDER_TYPE(cur_folder) != F_IMAP) break;
525 } else if (FOLDER_TYPE(folder) == F_NEWS) {
526 if (FOLDER_TYPE(cur_folder) != F_MH &&
527 FOLDER_TYPE(cur_folder) != F_IMAP &&
528 FOLDER_TYPE(cur_folder) != F_NEWS) break;
529 }
530 }
531
532 folder_list = g_list_insert(folder_list, folder, i);
533 priv = g_new0(FolderPrivData, 1);
534 priv->folder = folder;
535 folder_priv_list = g_list_insert(folder_priv_list, priv, i);
536}
537
538GList *folder_get_list(void)
539{
540 return folder_list;
541}
542
543gint folder_read_list(void)
544{
545 GNode *node;
546 XMLNode *xmlnode;
547 gchar *path;
548
549 path = folder_get_list_path();
550 if (!is_file_exist(path)) return -1;
551 node = xml_parse_file(path);
552 if (!node) return -1;
553
554 xmlnode = node->data;
555 if (strcmp2(xmlnode->tag->tag, "folderlist") != 0) {
556 g_warning("wrong folder list\n");
557 xml_free_tree(node);
558 return -1;
559 }
560
561 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2,
562 folder_read_folder_func, NULL);
563
564 xml_free_tree(node);
565 if (folder_list)
566 return 0;
567 else
568 return -1;
569}
570
571void folder_write_list(void)
572{
573 GList *list;
574 Folder *folder;
575 gchar *path;
576 PrefFile *pfile;
577
578 path = folder_get_list_path();
579 if ((pfile = prefs_file_open(path)) == NULL) return;
580
581 fprintf(pfile->fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n",
582 CS_INTERNAL);
583 fputs("\n<folderlist>\n", pfile->fp);
584
585 for (list = folder_list; list != NULL; list = list->next) {
586 folder = list->data;
587 folder_write_list_recursive(folder->node, pfile->fp);
588 }
589
590 fputs("</folderlist>\n", pfile->fp);
591
592 if (prefs_file_close(pfile) < 0)
593 g_warning("failed to write folder list.\n");
594
595 if (syl_app_get())
596 g_signal_emit_by_name(syl_app_get(), "folderlist-updated");
597}
598
599struct TotalMsgStatus
600{
601 guint new;
602 guint unread;
603 guint total;
604 GString *str;
605};
606
607static gboolean folder_get_status_full_all_func(GNode *node, gpointer data)
608{
609 FolderItem *item;
610 struct TotalMsgStatus *status = (struct TotalMsgStatus *)data;
611 gchar *id;
612
613 g_return_val_if_fail(node->data != NULL, FALSE);
614
615 item = FOLDER_ITEM(node->data);
616
617 if (!item->path) return FALSE;
618
619 status->new += item->new;
620 status->unread += item->unread;
621 status->total += item->total;
622
623 if (status->str) {
624 id = folder_item_get_identifier(item);
625 g_string_sprintfa(status->str, "%5d %5d %5d %s\n",
626 item->new, item->unread,
627 item->total, id);
628 g_free(id);
629 }
630
631 return FALSE;
632}
633
634static void folder_get_status_full_all(GString *str, guint *new, guint *unread,
635 guint *total)
636{
637 GList *list;
638 Folder *folder;
639 struct TotalMsgStatus status;
640
641 status.new = status.unread = status.total = 0;
642 status.str = str;
643
644 debug_print("Counting total number of messages...\n");
645
646 for (list = folder_list; list != NULL; list = list->next) {
647 folder = FOLDER(list->data);
648 if (folder->node)
649 g_node_traverse(folder->node, G_PRE_ORDER,
650 G_TRAVERSE_ALL, -1,
651 folder_get_status_full_all_func,
652 &status);
653 }
654
655 *new = status.new;
656 *unread = status.unread;
657 *total = status.total;
658}
659
660gchar *folder_get_status(GPtrArray *folders, gboolean full)
661{
662 guint new, unread, total;
663 GString *str;
664 gint i;
665 gchar *ret;
666
667 new = unread = total = 0;
668
669 str = g_string_new(NULL);
670
671 if (folders) {
672 for (i = 0; i < folders->len; i++) {
673 FolderItem *item;
674
675 item = g_ptr_array_index(folders, i);
676 new += item->new;
677 unread += item->unread;
678 total += item->total;
679
680 if (full) {
681 gchar *id;
682
683 id = folder_item_get_identifier(item);
684 g_string_sprintfa(str, "%5d %5d %5d %s\n",
685 item->new, item->unread,
686 item->total, id);
687 g_free(id);
688 }
689 }
690 } else {
691 folder_get_status_full_all(full ? str : NULL,
692 &new, &unread, &total);
693 }
694
695 if (full)
696 g_string_sprintfa(str, "%5d %5d %5d\n", new, unread, total);
697 else
698 g_string_sprintfa(str, "%d %d %d\n", new, unread, total);
699
700 ret = str->str;
701 g_string_free(str, FALSE);
702
703 return ret;
704}
705
706Folder *folder_find_from_path(const gchar *path)
707{
708 GList *list;
709 Folder *folder;
710
711 for (list = folder_list; list != NULL; list = list->next) {
712 folder = list->data;
713 if (FOLDER_TYPE(folder) == F_MH &&
714 !path_cmp(LOCAL_FOLDER(folder)->rootpath, path))
715 return folder;
716 }
717
718 return NULL;
719}
720
721Folder *folder_find_from_name(const gchar *name, FolderType type)
722{
723 GList *list;
724 Folder *folder;
725
726 for (list = folder_list; list != NULL; list = list->next) {
727 folder = list->data;
728 if (FOLDER_TYPE(folder) == type &&
729 strcmp2(name, folder->name) == 0)
730 return folder;
731 }
732
733 return NULL;
734}
735
736static gboolean folder_item_find_func(GNode *node, gpointer data)
737{
738 FolderItem *item = node->data;
739 gpointer *d = data;
740 const gchar *path = d[0];
741
742 if (path_cmp(path, item->path) != 0)
743 return FALSE;
744
745 d[1] = item;
746
747 return TRUE;
748}
749
750FolderItem *folder_find_item_from_path(const gchar *path)
751{
752 Folder *folder;
753 gpointer d[2];
754
755 folder = folder_get_default_folder();
756 g_return_val_if_fail(folder != NULL, NULL);
757
758 d[0] = (gpointer)path;
759 d[1] = NULL;
760 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
761 folder_item_find_func, d);
762 return d[1];
763}
764
765FolderItem *folder_find_child_item_by_name(FolderItem *item, const gchar *name)
766{
767 GNode *node;
768 FolderItem *child;
769 const gchar *base;
770
771 if (!name)
772 return NULL;
773
774 for (node = item->node->children; node != NULL; node = node->next) {
775 child = FOLDER_ITEM(node->data);
776 base = g_basename(child->path);
777#ifdef G_OS_WIN32
778 if (base && g_ascii_strcasecmp(base, name) == 0)
779#else
780 if (strcmp2(base, name) == 0)
781#endif
782 return child;
783 }
784
785 return NULL;
786}
787
788static const struct {
789 gchar *str;
790 FolderType type;
791} type_str_table[] = {
792 {"#mh" , F_MH},
793 {"#mbox" , F_MBOX},
794 {"#maildir", F_MAILDIR},
795 {"#imap" , F_IMAP},
796 {"#news" , F_NEWS}
797};
798
799static gchar *folder_get_type_string(FolderType type)
800{
801 gint i;
802
803 for (i = 0; i < sizeof(type_str_table) / sizeof(type_str_table[0]);
804 i++) {
805 if (type_str_table[i].type == type)
806 return type_str_table[i].str;
807 }
808
809 return NULL;
810}
811
812static FolderType folder_get_type_from_string(const gchar *str)
813{
814 gint i;
815
816 for (i = 0; i < sizeof(type_str_table) / sizeof(type_str_table[0]);
817 i++) {
818 if (g_ascii_strcasecmp(type_str_table[i].str, str) == 0)
819 return type_str_table[i].type;
820 }
821
822 return F_UNKNOWN;
823}
824
825gchar *folder_get_identifier(Folder *folder)
826{
827 gchar *type_str;
828
829 g_return_val_if_fail(folder != NULL, NULL);
830
831 type_str = folder_get_type_string(FOLDER_TYPE(folder));
832 return g_strconcat(type_str, "/", folder->name, NULL);
833}
834
835gchar *folder_item_get_identifier(FolderItem *item)
836{
837 gchar *id;
838 gchar *folder_id;
839
840 g_return_val_if_fail(item != NULL, NULL);
841
842 if (!item->path) {
843 if (!item->parent)
844 return folder_get_identifier(item->folder);
845 else
846 return NULL;
847 }
848
849 folder_id = folder_get_identifier(item->folder);
850 id = g_strconcat(folder_id, "/", item->path, NULL);
851 g_free(folder_id);
852
853 return id;
854}
855
856FolderItem *folder_find_item_from_identifier(const gchar *identifier)
857{
858 Folder *folder;
859 gpointer d[2];
860 gchar *str;
861 gchar *p;
862 gchar *name;
863 gchar *path;
864 FolderType type;
865
866 g_return_val_if_fail(identifier != NULL, NULL);
867
868 if (*identifier != '#')
869 return folder_find_item_from_path(identifier);
870
871 Xstrdup_a(str, identifier, return NULL);
872
873 p = strchr(str, '/');
874 if (!p)
875 return folder_find_item_from_path(identifier);
876 *p = '\0';
877 p++;
878 type = folder_get_type_from_string(str);
879 if (type == F_UNKNOWN)
880 return folder_find_item_from_path(identifier);
881
882 name = p;
883 p = strchr(p, '/');
884 if (p) {
885 *p = '\0';
886 p++;
887 }
888
889 folder = folder_find_from_name(name, type);
890 if (!folder)
891 return folder_find_item_from_path(identifier);
892
893 if (!p)
894 return FOLDER_ITEM(folder->node->data);
895
896 path = p;
897
898 d[0] = (gpointer)path;
899 d[1] = NULL;
900 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
901 folder_item_find_func, d);
902 return d[1];
903}
904
905FolderItem *folder_find_item_and_num_from_id(const gchar *identifier, gint *num)
906{
907 gchar *id;
908 gchar *msg;
909 FolderItem *item;
910
911 g_return_val_if_fail(identifier != NULL, NULL);
912
913 id = g_path_get_dirname(identifier);
914 msg = g_path_get_basename(identifier);
915 item = folder_find_item_from_identifier(id);
916 *num = to_number(msg);
917 g_free(msg);
918 g_free(id);
919
920 return item;
921}
922
923Folder *folder_get_default_folder(void)
924{
925 return folder_list ? FOLDER(folder_list->data) : NULL;
926}
927
928FolderItem *folder_get_default_inbox(void)
929{
930 Folder *folder;
931
932 if (!folder_list) return NULL;
933 folder = FOLDER(folder_list->data);
934 g_return_val_if_fail(folder != NULL, NULL);
935 return folder->inbox;
936}
937
938FolderItem *folder_get_default_outbox(void)
939{
940 Folder *folder;
941
942 if (!folder_list) return NULL;
943 folder = FOLDER(folder_list->data);
944 g_return_val_if_fail(folder != NULL, NULL);
945 return folder->outbox;
946}
947
948FolderItem *folder_get_default_draft(void)
949{
950 Folder *folder;
951
952 if (!folder_list) return NULL;
953 folder = FOLDER(folder_list->data);
954 g_return_val_if_fail(folder != NULL, NULL);
955 return folder->draft;
956}
957
958FolderItem *folder_get_default_queue(void)
959{
960 Folder *folder;
961
962 if (!folder_list) return NULL;
963 folder = FOLDER(folder_list->data);
964 g_return_val_if_fail(folder != NULL, NULL);
965 return folder->queue;
966}
967
968FolderItem *folder_get_default_trash(void)
969{
970 Folder *folder;
971
972 if (!folder_list) return NULL;
973 folder = FOLDER(folder_list->data);
974 g_return_val_if_fail(folder != NULL, NULL);
975 return folder->trash;
976}
977
978FolderItem *folder_get_default_junk(void)
979{
980 FolderPrivData *priv;
981
982 if (!folder_list) return NULL;
983 if (!folder_priv_list) return NULL;
984 priv = (FolderPrivData *)folder_priv_list->data;
985 g_return_val_if_fail(priv != NULL, NULL);
986 g_return_val_if_fail(priv->folder != NULL, NULL);
987 return priv->junk;
988}
989
990static FolderPrivData *folder_get_priv(Folder *folder)
991{
992 FolderPrivData *priv;
993 GList *cur;
994
995 g_return_val_if_fail(folder != NULL, NULL);
996
997 for (cur = folder_priv_list; cur != NULL; cur = cur->next) {
998 priv = (FolderPrivData *)cur->data;
999 if (priv->folder == folder)
1000 return priv;
1001 }
1002
1003 g_warning("folder_get_priv: private data for Folder (%p) not found.",
1004 folder);
1005
1006 return NULL;
1007}
1008
1009FolderItem *folder_get_junk(Folder *folder)
1010{
1011 FolderPrivData *priv;
1012
1013 priv = folder_get_priv(folder);
1014 if (priv)
1015 return priv->junk;
1016
1017 return NULL;
1018}
1019
1020void folder_set_junk(Folder *folder, FolderItem *item)
1021{
1022 FolderPrivData *priv;
1023
1024 priv = folder_get_priv(folder);
1025 if (priv)
1026 priv->junk = item;
1027}
1028
1029gboolean folder_item_is_trash(FolderItem *item)
1030{
1031 PrefsAccount *ac;
1032 FolderItem *trash;
1033
1034 g_return_val_if_fail(item != NULL, FALSE);
1035
1036 if (item->stype == F_TRASH)
1037 return TRUE;
1038
1039 ac = account_find_from_item_property(item);
1040 if (ac && ac->set_trash_folder && ac->trash_folder) {
1041 trash = folder_find_item_from_identifier(ac->trash_folder);
1042 if (trash == item)
1043 return TRUE;
1044 }
1045
1046 return FALSE;
1047}
1048
1049#define CREATE_FOLDER_IF_NOT_EXIST(member, dir, type) \
1050{ \
1051 if (!folder->member) { \
1052 item = folder_item_new(dir, dir); \
1053 item->stype = type; \
1054 folder_item_append(rootitem, item); \
1055 folder->member = item; \
1056 } \
1057}
1058
1059void folder_set_missing_folders(void)
1060{
1061 Folder *folder;
1062 FolderItem *rootitem;
1063 FolderItem *item;
1064 GList *list;
1065
1066 for (list = folder_list; list != NULL; list = list->next) {
1067 folder = list->data;
1068 if (FOLDER_TYPE(folder) != F_MH) continue;
1069 rootitem = FOLDER_ITEM(folder->node->data);
1070 g_return_if_fail(rootitem != NULL);
1071
1072 if (folder->inbox && folder->outbox && folder->draft &&
1073 folder->queue && folder->trash && folder_get_junk(folder))
1074 continue;
1075
1076 if (folder_create_tree(folder) < 0) {
1077 g_warning("%s: can't create the folder tree.\n",
1078 LOCAL_FOLDER(folder)->rootpath);
1079 continue;
1080 }
1081
1082 CREATE_FOLDER_IF_NOT_EXIST(inbox, INBOX_DIR, F_INBOX);
1083 CREATE_FOLDER_IF_NOT_EXIST(outbox, OUTBOX_DIR, F_OUTBOX);
1084 CREATE_FOLDER_IF_NOT_EXIST(draft, DRAFT_DIR, F_DRAFT);
1085 CREATE_FOLDER_IF_NOT_EXIST(queue, QUEUE_DIR, F_QUEUE);
1086 CREATE_FOLDER_IF_NOT_EXIST(trash, TRASH_DIR, F_TRASH);
1087
1088 if (!folder_get_junk(folder)) {
1089 item = folder_item_new(JUNK_DIR, JUNK_DIR);
1090 item->stype = F_JUNK;
1091 folder_item_append(rootitem, item);
1092 folder_set_junk(folder, item);
1093 }
1094 }
1095}
1096
1097static gboolean folder_unref_account_func(GNode *node, gpointer data)
1098{
1099 FolderItem *item = node->data;
1100 PrefsAccount *account = data;
1101
1102 if (item->account == account)
1103 item->account = NULL;
1104
1105 return FALSE;
1106}
1107
1108void folder_unref_account_all(PrefsAccount *account)
1109{
1110 Folder *folder;
1111 GList *list;
1112
1113 if (!account) return;
1114
1115 for (list = folder_list; list != NULL; list = list->next) {
1116 folder = list->data;
1117 if (folder->account == account)
1118 folder->account = NULL;
1119 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1120 folder_unref_account_func, account);
1121 }
1122}
1123
1124#undef CREATE_FOLDER_IF_NOT_EXIST
1125
1126gchar *folder_get_path(Folder *folder)
1127{
1128 gchar *path;
1129
1130 g_return_val_if_fail(folder != NULL, NULL);
1131
1132 if (FOLDER_TYPE(folder) == F_MH) {
1133 path = g_filename_from_utf8(LOCAL_FOLDER(folder)->rootpath,
1134 -1, NULL, NULL, NULL);
1135 if (!path) {
1136 g_warning("folder_get_path: failed to convert character set\n");
1137 path = g_strdup(LOCAL_FOLDER(folder)->rootpath);
1138 }
1139 if (!g_path_is_absolute(path)) {
1140 gchar *path_;
1141
1142 path_ = g_strconcat(get_mail_base_dir(),
1143 G_DIR_SEPARATOR_S, path, NULL);
1144 g_free(path);
1145 path = path_;
1146 }
1147 } else if (FOLDER_TYPE(folder) == F_IMAP) {
1148 gchar *server;
1149 gchar *uid;
1150
1151 g_return_val_if_fail(folder->account != NULL, NULL);
1152 server = uriencode_for_filename(folder->account->recv_server);
1153 uid = uriencode_for_filename(folder->account->userid);
1154 path = g_strconcat(get_imap_cache_dir(),
1155 G_DIR_SEPARATOR_S, server,
1156 G_DIR_SEPARATOR_S, uid, NULL);
1157 g_free(uid);
1158 g_free(server);
1159 } else if (FOLDER_TYPE(folder) == F_NEWS) {
1160 gchar *server;
1161
1162 g_return_val_if_fail(folder->account != NULL, NULL);
1163 server = uriencode_for_filename(folder->account->nntp_server);
1164 path = g_strconcat(get_news_cache_dir(),
1165 G_DIR_SEPARATOR_S, server, NULL);
1166 g_free(server);
1167 } else
1168 path = NULL;
1169
1170 return path;
1171}
1172
1173gchar *folder_item_get_path(FolderItem *item)
1174{
1175 gchar *folder_path;
1176 gchar *item_path = NULL, *path;
1177
1178 g_return_val_if_fail(item != NULL, NULL);
1179
1180 folder_path = folder_get_path(item->folder);
1181 g_return_val_if_fail(folder_path != NULL, NULL);
1182
1183 if (item->path) {
1184 item_path = g_filename_from_utf8(item->path, -1,
1185 NULL, NULL, NULL);
1186 if (!item_path) {
1187 g_warning("folder_item_get_path: failed to convert character set\n");
1188 item_path = g_strdup(item->path);
1189 }
1190#ifdef G_OS_WIN32
1191 subst_char(item_path, '/', G_DIR_SEPARATOR);
1192#endif
1193 }
1194
1195 if (item_path)
1196 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S, item_path,
1197 NULL);
1198 else
1199 path = g_strdup(folder_path);
1200
1201 g_free(item_path);
1202 g_free(folder_path);
1203 return path;
1204}
1205
1206gint folder_item_scan(FolderItem *item)
1207{
1208 Folder *folder;
1209
1210 g_return_val_if_fail(item != NULL, -1);
1211
1212 folder = item->folder;
1213 return folder->klass->scan(folder, item);
1214}
1215
1216static void folder_item_scan_foreach_func(gpointer key, gpointer val,
1217 gpointer data)
1218{
1219 folder_item_scan(FOLDER_ITEM(key));
1220}
1221
1222void folder_item_scan_foreach(GHashTable *table)
1223{
1224 g_hash_table_foreach(table, folder_item_scan_foreach_func, NULL);
1225}
1226
1227GSList *folder_item_get_msg_list(FolderItem *item, gboolean use_cache)
1228{
1229 Folder *folder;
1230
1231 g_return_val_if_fail(item != NULL, NULL);
1232
1233 folder = item->folder;
1234
1235 if (item->stype == F_VIRTUAL)
1236 return virtual_get_class()->get_msg_list(folder, item,
1237 use_cache);
1238
1239 return folder->klass->get_msg_list(folder, item, use_cache);
1240}
1241
1242GSList *folder_item_get_uncached_msg_list(FolderItem *item)
1243{
1244 Folder *folder;
1245
1246 g_return_val_if_fail(item != NULL, NULL);
1247 g_return_val_if_fail(item->folder->klass->get_uncached_msg_list != NULL,
1248 NULL);
1249
1250 folder = item->folder;
1251
1252 if (item->stype == F_VIRTUAL)
1253 return NULL;
1254
1255 return folder->klass->get_uncached_msg_list(folder, item);
1256}
1257
1258gchar *folder_item_fetch_msg(FolderItem *item, gint num)
1259{
1260 Folder *folder;
1261
1262 g_return_val_if_fail(item != NULL, NULL);
1263
1264 folder = item->folder;
1265
1266 return folder->klass->fetch_msg(folder, item, num);
1267}
1268
1269gint folder_item_fetch_all_msg(FolderItem *item)
1270{
1271 Folder *folder;
1272 GSList *mlist;
1273 GSList *cur;
1274 gint num = 0;
1275 gint ret = 0;
1276
1277 g_return_val_if_fail(item != NULL, -1);
1278
1279 debug_print("fetching all messages in %s ...\n", item->path);
1280
1281 folder = item->folder;
1282
1283 if (folder->ui_func)
1284 folder->ui_func(folder, item, folder->ui_func_data ?
1285 folder->ui_func_data : GINT_TO_POINTER(num));
1286
1287 mlist = folder_item_get_msg_list(item, TRUE);
1288
1289 for (cur = mlist; cur != NULL; cur = cur->next) {
1290 MsgInfo *msginfo = (MsgInfo *)cur->data;
1291 gchar *msg;
1292
1293 num++;
1294 if (folder->ui_func)
1295 folder->ui_func(folder, item,
1296 folder->ui_func_data ?
1297 folder->ui_func_data :
1298 GINT_TO_POINTER(num));
1299
1300 msg = folder_item_fetch_msg(item, msginfo->msgnum);
1301 if (!msg) {
1302 g_warning("Can't fetch message %d. Aborting.\n",
1303 msginfo->msgnum);
1304 ret = -1;
1305 break;
1306 }
1307 g_free(msg);
1308 }
1309
1310 procmsg_msg_list_free(mlist);
1311
1312 return ret;
1313}
1314
1315MsgInfo *folder_item_get_msginfo(FolderItem *item, gint num)
1316{
1317 Folder *folder;
1318
1319 g_return_val_if_fail(item != NULL, NULL);
1320
1321 folder = item->folder;
1322
1323 return folder->klass->get_msginfo(folder, item, num);
1324}
1325
1326gint folder_item_add_msg(FolderItem *dest, const gchar *file, MsgFlags *flags,
1327 gboolean remove_source)
1328{
1329 Folder *folder;
1330
1331 g_return_val_if_fail(dest != NULL, -1);
1332 g_return_val_if_fail(file != NULL, -1);
1333 g_return_val_if_fail(dest->folder->klass->add_msg != NULL, -1);
1334
1335 folder = dest->folder;
1336
1337 return folder->klass->add_msg(folder, dest, file, flags, remove_source);
1338}
1339
1340gint folder_item_add_msgs(FolderItem *dest, GSList *file_list,
1341 gboolean remove_source, gint *first)
1342{
1343 Folder *folder;
1344
1345 g_return_val_if_fail(dest != NULL, -1);
1346 g_return_val_if_fail(file_list != NULL, -1);
1347 g_return_val_if_fail(dest->folder->klass->add_msgs != NULL, -1);
1348
1349 folder = dest->folder;
1350
1351 return folder->klass->add_msgs(folder, dest, file_list, remove_source,
1352 first);
1353}
1354
1355gint folder_item_add_msg_msginfo(FolderItem *dest, MsgInfo *msginfo,
1356 gboolean remove_source)
1357{
1358 Folder *folder;
1359
1360 g_return_val_if_fail(dest != NULL, -1);
1361 g_return_val_if_fail(msginfo != NULL, -1);
1362 g_return_val_if_fail(msginfo->file_path != NULL, -1);
1363 g_return_val_if_fail(dest->folder->klass->add_msg_msginfo != NULL, -1);
1364
1365 folder = dest->folder;
1366
1367 return folder->klass->add_msg_msginfo(folder, dest, msginfo,
1368 remove_source);
1369}
1370
1371gint folder_item_add_msgs_msginfo(FolderItem *dest, GSList *msglist,
1372 gboolean remove_source, gint *first)
1373{
1374 Folder *folder;
1375
1376 g_return_val_if_fail(dest != NULL, -1);
1377 g_return_val_if_fail(msglist != NULL, -1);
1378 g_return_val_if_fail(dest->folder->klass->add_msgs_msginfo != NULL, -1);
1379
1380 folder = dest->folder;
1381
1382 return folder->klass->add_msgs_msginfo(folder, dest, msglist,
1383 remove_source, first);
1384}
1385
1386#define IS_FROM_QUEUE(m, d) \
1387 (m->folder && m->folder->stype == F_QUEUE && \
1388 MSG_IS_QUEUED(m->flags) && d->stype != F_QUEUE)
1389
1390gint folder_item_move_msg(FolderItem *dest, MsgInfo *msginfo)
1391{
1392 Folder *folder;
1393
1394 g_return_val_if_fail(dest != NULL, -1);
1395 g_return_val_if_fail(msginfo != NULL, -1);
1396 g_return_val_if_fail(dest->folder->klass->move_msg != NULL, -1);
1397
1398 folder = dest->folder;
1399
1400 if (IS_FROM_QUEUE(msginfo, dest)) {
1401 GSList msglist;
1402
1403 msglist.data = msginfo;
1404 msglist.next = NULL;
1405 return procmsg_add_messages_from_queue(dest, &msglist, TRUE);
1406 }
1407
1408 return folder->klass->move_msg(folder, dest, msginfo);
1409}
1410
1411gint folder_item_move_msgs(FolderItem *dest, GSList *msglist)
1412{
1413 Folder *folder;
1414 MsgInfo *msginfo;
1415
1416 g_return_val_if_fail(dest != NULL, -1);
1417 g_return_val_if_fail(msglist != NULL, -1);
1418 g_return_val_if_fail(dest->folder->klass->move_msgs != NULL, -1);
1419
1420 folder = dest->folder;
1421
1422 msginfo = (MsgInfo *)msglist->data;
1423 if (IS_FROM_QUEUE(msginfo, dest))
1424 return procmsg_add_messages_from_queue(dest, msglist, TRUE);
1425
1426 return folder->klass->move_msgs(folder, dest, msglist);
1427}
1428
1429gint folder_item_copy_msg(FolderItem *dest, MsgInfo *msginfo)
1430{
1431 Folder *folder;
1432
1433 g_return_val_if_fail(dest != NULL, -1);
1434 g_return_val_if_fail(msginfo != NULL, -1);
1435 g_return_val_if_fail(dest->folder->klass->copy_msg != NULL, -1);
1436
1437 folder = dest->folder;
1438
1439 if (IS_FROM_QUEUE(msginfo, dest)) {
1440 GSList msglist;
1441
1442 msglist.data = msginfo;
1443 msglist.next = NULL;
1444 return procmsg_add_messages_from_queue(dest, &msglist, FALSE);
1445 }
1446
1447 return folder->klass->copy_msg(folder, dest, msginfo);
1448}
1449
1450gint folder_item_copy_msgs(FolderItem *dest, GSList *msglist)
1451{
1452 Folder *folder;
1453 MsgInfo *msginfo;
1454
1455 g_return_val_if_fail(dest != NULL, -1);
1456 g_return_val_if_fail(msglist != NULL, -1);
1457 g_return_val_if_fail(dest->folder->klass->copy_msgs != NULL, -1);
1458
1459 folder = dest->folder;
1460
1461 msginfo = (MsgInfo *)msglist->data;
1462 if (IS_FROM_QUEUE(msginfo, dest))
1463 return procmsg_add_messages_from_queue(dest, msglist, FALSE);
1464
1465 return folder->klass->copy_msgs(folder, dest, msglist);
1466}
1467
1468#undef IS_FROM_QUEUE
1469
1470gint folder_item_remove_msg(FolderItem *item, MsgInfo *msginfo)
1471{
1472 Folder *folder;
1473
1474 g_return_val_if_fail(item != NULL, -1);
1475 g_return_val_if_fail(item->folder->klass->remove_msg != NULL, -1);
1476
1477 folder = item->folder;
1478
1479 return folder->klass->remove_msg(folder, item, msginfo);
1480}
1481
1482gint folder_item_remove_msgs(FolderItem *item, GSList *msglist)
1483{
1484 Folder *folder;
1485 gint ret = 0;
1486
1487 g_return_val_if_fail(item != NULL, -1);
1488
1489 folder = item->folder;
1490 if (folder->klass->remove_msgs) {
1491 return folder->klass->remove_msgs(folder, item, msglist);
1492 }
1493
1494 while (msglist != NULL) {
1495 MsgInfo *msginfo = (MsgInfo *)msglist->data;
1496
1497 ret = folder_item_remove_msg(item, msginfo);
1498 if (ret != 0) break;
1499 msglist = msglist->next;
1500 }
1501
1502 return ret;
1503}
1504
1505gint folder_item_remove_all_msg(FolderItem *item)
1506{
1507 Folder *folder;
1508
1509 g_return_val_if_fail(item != NULL, -1);
1510 g_return_val_if_fail(item->folder->klass->remove_all_msg != NULL, -1);
1511
1512 folder = item->folder;
1513
1514 return folder->klass->remove_all_msg(folder, item);
1515}
1516
1517gboolean folder_item_is_msg_changed(FolderItem *item, MsgInfo *msginfo)
1518{
1519 Folder *folder;
1520
1521 g_return_val_if_fail(item != NULL, FALSE);
1522 g_return_val_if_fail(item->folder->klass->is_msg_changed != NULL,
1523 FALSE);
1524
1525 folder = item->folder;
1526 return folder->klass->is_msg_changed(folder, item, msginfo);
1527}
1528
1529gint folder_item_close(FolderItem *item)
1530{
1531 Folder *folder;
1532
1533 g_return_val_if_fail(item != NULL, -1);
1534
1535 item->opened = FALSE;
1536 folder = item->folder;
1537 return folder->klass->close(folder, item);
1538}
1539
1540gchar *folder_item_get_cache_file(FolderItem *item)
1541{
1542 gchar *path;
1543 gchar *file;
1544
1545 g_return_val_if_fail(item != NULL, NULL);
1546 g_return_val_if_fail(item->path != NULL, NULL);
1547
1548 path = folder_item_get_path(item);
1549 g_return_val_if_fail(path != NULL, NULL);
1550 if (!is_dir_exist(path))
1551 make_dir_hier(path);
1552 file = g_strconcat(path, G_DIR_SEPARATOR_S, CACHE_FILE, NULL);
1553 g_free(path);
1554
1555 return file;
1556}
1557
1558gchar *folder_item_get_mark_file(FolderItem *item)
1559{
1560 gchar *path;
1561 gchar *file;
1562
1563 g_return_val_if_fail(item != NULL, NULL);
1564 g_return_val_if_fail(item->path != NULL, NULL);
1565
1566 path = folder_item_get_path(item);
1567 g_return_val_if_fail(path != NULL, NULL);
1568 if (!is_dir_exist(path))
1569 make_dir_hier(path);
1570 file = g_strconcat(path, G_DIR_SEPARATOR_S, MARK_FILE, NULL);
1571 g_free(path);
1572
1573 return file;
1574}
1575
1576static gboolean folder_build_tree(GNode *node, gpointer data)
1577{
1578 Folder *folder = FOLDER(data);
1579 FolderItem *item;
1580 XMLNode *xmlnode;
1581 GList *list;
1582 SpecialFolderItemType stype = F_NORMAL;
1583 const gchar *name = NULL;
1584 const gchar *path = NULL;
1585 PrefsAccount *account = NULL;
1586 gboolean no_sub = FALSE, no_select = FALSE, collapsed = FALSE,
1587 threaded = TRUE, ac_apply_sub = FALSE;
1588 FolderSortKey sort_key = SORT_BY_NONE;
1589 FolderSortType sort_type = SORT_ASCENDING;
1590 gboolean qsearch_cond_type = 0;
1591 gint new = 0, unread = 0, total = 0;
1592 time_t mtime = 0;
1593 gboolean use_auto_to_on_reply = FALSE;
1594 gchar *auto_to = NULL, *auto_cc = NULL, *auto_bcc = NULL,
1595 *auto_replyto = NULL;
1596 gboolean trim_summary_subject = FALSE, trim_compose_subject = FALSE;
1597
1598 g_return_val_if_fail(node->data != NULL, FALSE);
1599 if (!node->parent) return FALSE;
1600
1601 xmlnode = node->data;
1602 if (strcmp2(xmlnode->tag->tag, "folderitem") != 0) {
1603 g_warning("tag name != \"folderitem\"\n");
1604 return FALSE;
1605 }
1606
1607 list = xmlnode->tag->attr;
1608 for (; list != NULL; list = list->next) {
1609 XMLAttr *attr = list->data;
1610
1611 if (!attr || !attr->name || !attr->value) continue;
1612 if (!strcmp(attr->name, "type")) {
1613 if (!g_ascii_strcasecmp(attr->value, "normal"))
1614 stype = F_NORMAL;
1615 else if (!g_ascii_strcasecmp(attr->value, "inbox"))
1616 stype = F_INBOX;
1617 else if (!g_ascii_strcasecmp(attr->value, "outbox"))
1618 stype = F_OUTBOX;
1619 else if (!g_ascii_strcasecmp(attr->value, "draft"))
1620 stype = F_DRAFT;
1621 else if (!g_ascii_strcasecmp(attr->value, "queue"))
1622 stype = F_QUEUE;
1623 else if (!g_ascii_strcasecmp(attr->value, "trash"))
1624 stype = F_TRASH;
1625 else if (!g_ascii_strcasecmp(attr->value, "junk"))
1626 stype = F_JUNK;
1627 else if (!g_ascii_strcasecmp(attr->value, "virtual"))
1628 stype = F_VIRTUAL;
1629 } else if (!strcmp(attr->name, "name"))
1630 name = attr->value;
1631 else if (!strcmp(attr->name, "path")) {
1632#ifdef G_OS_WIN32
1633 subst_char(attr->value, G_DIR_SEPARATOR, '/');
1634#endif
1635 path = attr->value;
1636 } else if (!strcmp(attr->name, "mtime"))
1637 mtime = strtoll(attr->value, NULL, 10);
1638 else if (!strcmp(attr->name, "new"))
1639 new = atoi(attr->value);
1640 else if (!strcmp(attr->name, "unread"))
1641 unread = atoi(attr->value);
1642 else if (!strcmp(attr->name, "total"))
1643 total = atoi(attr->value);
1644 else if (!strcmp(attr->name, "no_sub"))
1645 no_sub = *attr->value == '1' ? TRUE : FALSE;
1646 else if (!strcmp(attr->name, "no_select"))
1647 no_select = *attr->value == '1' ? TRUE : FALSE;
1648 else if (!strcmp(attr->name, "collapsed"))
1649 collapsed = *attr->value == '1' ? TRUE : FALSE;
1650 else if (!strcmp(attr->name, "threaded"))
1651 threaded = *attr->value == '1' ? TRUE : FALSE;
1652 else if (!strcmp(attr->name, "sort_key")) {
1653 if (!strcmp(attr->value, "none"))
1654 sort_key = SORT_BY_NONE;
1655 else if (!strcmp(attr->value, "number"))
1656 sort_key = SORT_BY_NUMBER;
1657 else if (!strcmp(attr->value, "size"))
1658 sort_key = SORT_BY_SIZE;
1659 else if (!strcmp(attr->value, "date"))
1660 sort_key = SORT_BY_DATE;
1661 else if (!strcmp(attr->value, "thread-date"))
1662 sort_key = SORT_BY_TDATE;
1663 else if (!strcmp(attr->value, "from"))
1664 sort_key = SORT_BY_FROM;
1665 else if (!strcmp(attr->value, "subject"))
1666 sort_key = SORT_BY_SUBJECT;
1667 else if (!strcmp(attr->value, "score"))
1668 sort_key = SORT_BY_SCORE;
1669 else if (!strcmp(attr->value, "label"))
1670 sort_key = SORT_BY_LABEL;
1671 else if (!strcmp(attr->value, "mark"))
1672 sort_key = SORT_BY_MARK;
1673 else if (!strcmp(attr->value, "unread"))
1674 sort_key = SORT_BY_UNREAD;
1675 else if (!strcmp(attr->value, "mime"))
1676 sort_key = SORT_BY_MIME;
1677 else if (!strcmp(attr->value, "to"))
1678 sort_key = SORT_BY_TO;
1679 } else if (!strcmp(attr->name, "sort_type")) {
1680 if (!strcmp(attr->value, "ascending"))
1681 sort_type = SORT_ASCENDING;
1682 else
1683 sort_type = SORT_DESCENDING;
1684 } else if (!strcmp(attr->name, "qsearch_cond")) {
1685 if (!strcmp(attr->value, "all"))
1686 qsearch_cond_type = 0;
1687 else if (!strcmp(attr->value, "unread"))
1688 qsearch_cond_type = 1;
1689 else if (!strcmp(attr->value, "mark"))
1690 qsearch_cond_type = 2;
1691 else if (!strcmp(attr->value, "clabel"))
1692 qsearch_cond_type = 3;
1693 else if (!strcmp(attr->value, "mime"))
1694 qsearch_cond_type = 4;
1695 else if (!strcmp(attr->value, "w1day"))
1696 qsearch_cond_type = 5;
1697 else if (!strcmp(attr->value, "last5"))
1698 qsearch_cond_type = 6;
1699 else if (!strcmp(attr->value, "last7"))
1700 qsearch_cond_type = 7;
1701 else if (!strcmp(attr->value, "in-addressbook"))
1702 qsearch_cond_type = 8;
1703 else if (!strcmp(attr->value, "last30"))
1704 qsearch_cond_type = 9;
1705 } else if (!strcmp(attr->name, "account_id")) {
1706 account = account_find_from_id(atoi(attr->value));
1707 if (!account) g_warning("account_id: %s not found\n",
1708 attr->value);
1709 } else if (!strcmp(attr->name, "account_apply_sub"))
1710 ac_apply_sub = *attr->value == '1' ? TRUE : FALSE;
1711 else if (!strcmp(attr->name, "to"))
1712 auto_to = g_strdup(attr->value);
1713 else if (!strcmp(attr->name, "use_auto_to_on_reply"))
1714 use_auto_to_on_reply =
1715 *attr->value == '1' ? TRUE : FALSE;
1716 else if (!strcmp(attr->name, "cc"))
1717 auto_cc = g_strdup(attr->value);
1718 else if (!strcmp(attr->name, "bcc"))
1719 auto_bcc = g_strdup(attr->value);
1720 else if (!strcmp(attr->name, "replyto"))
1721 auto_replyto = g_strdup(attr->value);
1722 else if (!strcmp(attr->name, "trim_summary_subject")) {
1723 trim_summary_subject =
1724 *attr->value == '1' ? TRUE : FALSE;
1725 } else if (!strcmp(attr->name, "trim_compose_subject")) {
1726 trim_compose_subject =
1727 *attr->value = '1' ? TRUE : FALSE;
1728 }
1729 }
1730
1731 item = folder_item_new(name, path);
1732 item->stype = stype;
1733 item->mtime = mtime;
1734 item->new = new;
1735 item->unread = unread;
1736 item->total = total;
1737 item->no_sub = no_sub;
1738 item->no_select = no_select;
1739 item->collapsed = collapsed;
1740 item->threaded = threaded;
1741 item->sort_key = sort_key;
1742 item->sort_type = sort_type;
1743 item->node = node;
1744 item->parent = FOLDER_ITEM(node->parent->data);
1745 item->folder = folder;
1746 switch (stype) {
1747 case F_INBOX: folder->inbox = item; break;
1748 case F_OUTBOX: folder->outbox = item; break;
1749 case F_DRAFT: folder->draft = item; break;
1750 case F_QUEUE: folder->queue = item; break;
1751 case F_TRASH: folder->trash = item; break;
1752 case F_JUNK: folder_set_junk(folder, item); break;
1753 default: break;
1754 }
1755 item->account = account;
1756 item->ac_apply_sub = ac_apply_sub;
1757 item->auto_to = auto_to;
1758 item->use_auto_to_on_reply = use_auto_to_on_reply;
1759 item->auto_cc = auto_cc;
1760 item->auto_bcc = auto_bcc;
1761 item->auto_replyto = auto_replyto;
1762 item->trim_summary_subject = trim_summary_subject;
1763 item->trim_compose_subject = trim_compose_subject;
1764 item->qsearch_cond_type = qsearch_cond_type;
1765 node->data = item;
1766 xml_free_node(xmlnode);
1767
1768 return FALSE;
1769}
1770
1771static gboolean folder_read_folder_func(GNode *node, gpointer data)
1772{
1773 Folder *folder;
1774 FolderItem *item;
1775 XMLNode *xmlnode;
1776 GList *list;
1777 FolderType type = F_UNKNOWN;
1778 const gchar *name = NULL;
1779 const gchar *path = NULL;
1780 PrefsAccount *account = NULL;
1781 gboolean collapsed = FALSE, threaded = TRUE, ac_apply_sub = FALSE;
1782
1783 if (g_node_depth(node) != 2) return FALSE;
1784 g_return_val_if_fail(node->data != NULL, FALSE);
1785
1786 xmlnode = node->data;
1787 if (strcmp2(xmlnode->tag->tag, "folder") != 0) {
1788 g_warning("tag name != \"folder\"\n");
1789 return TRUE;
1790 }
1791 g_node_unlink(node);
1792 list = xmlnode->tag->attr;
1793 for (; list != NULL; list = list->next) {
1794 XMLAttr *attr = list->data;
1795
1796 if (!attr || !attr->name || !attr->value) continue;
1797 if (!strcmp(attr->name, "type")) {
1798 if (!g_ascii_strcasecmp(attr->value, "mh"))
1799 type = F_MH;
1800 else if (!g_ascii_strcasecmp(attr->value, "mbox"))
1801 type = F_MBOX;
1802 else if (!g_ascii_strcasecmp(attr->value, "maildir"))
1803 type = F_MAILDIR;
1804 else if (!g_ascii_strcasecmp(attr->value, "imap"))
1805 type = F_IMAP;
1806 else if (!g_ascii_strcasecmp(attr->value, "news"))
1807 type = F_NEWS;
1808 } else if (!strcmp(attr->name, "name"))
1809 name = attr->value;
1810 else if (!strcmp(attr->name, "path"))
1811 path = attr->value;
1812 else if (!strcmp(attr->name, "collapsed"))
1813 collapsed = *attr->value == '1' ? TRUE : FALSE;
1814 else if (!strcmp(attr->name, "threaded"))
1815 threaded = *attr->value == '1' ? TRUE : FALSE;
1816 else if (!strcmp(attr->name, "account_id")) {
1817 account = account_find_from_id(atoi(attr->value));
1818 if (!account) g_warning("account_id: %s not found\n",
1819 attr->value);
1820 } else if (!strcmp(attr->name, "account_apply_sub"))
1821 ac_apply_sub = *attr->value == '1' ? TRUE : FALSE;
1822 }
1823
1824 folder = folder_new(type, name, path);
1825 g_return_val_if_fail(folder != NULL, FALSE);
1826 if (account && FOLDER_IS_REMOTE(folder)) {
1827 folder->account = account;
1828 account->folder = REMOTE_FOLDER(folder);
1829 }
1830 if (account && FOLDER_IS_LOCAL(folder))
1831 ac_apply_sub = TRUE;
1832 item = FOLDER_ITEM(folder->node->data);
1833 node->data = item;
1834 item->node = node;
1835 g_node_destroy(folder->node);
1836 folder->node = node;
1837 folder_add(folder);
1838 item->collapsed = collapsed;
1839 item->threaded = threaded;
1840 item->account = account;
1841 item->ac_apply_sub = ac_apply_sub;
1842
1843 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1844 folder_build_tree, folder);
1845
1846 return FALSE;
1847}
1848
1849static gchar *folder_get_list_path(void)
1850{
1851 static gchar *filename = NULL;
1852
1853 if (!filename)
1854 filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1855 FOLDER_LIST, NULL);
1856
1857 return filename;
1858}
1859
1860#define PUT_ESCAPE_STR(fp, attr, str) \
1861{ \
1862 fputs(" " attr "=\"", fp); \
1863 xml_file_put_escape_str(fp, str); \
1864 fputs("\"", fp); \
1865}
1866
1867static void folder_write_list_recursive(GNode *node, gpointer data)
1868{
1869 FILE *fp = (FILE *)data;
1870 FolderItem *item;
1871 gint i, depth;
1872 static gchar *folder_type_str[] = {"mh", "mbox", "maildir", "imap",
1873 "news", "unknown"};
1874 static gchar *folder_item_stype_str[] = {"normal", "inbox", "outbox",
1875 "draft", "queue", "trash",
1876 "junk", "virtual"};
1877 static gchar *sort_key_str[] = {"none", "number", "size", "date",
1878 "thread-date",
1879 "from", "subject", "score", "label",
1880 "mark", "unread", "mime", "to"};
1881 static gchar *qsearch_cond_str[] = {"all", "unread", "mark", "clabel",
1882 "mime", "w1day", "last5", "last7",
1883 "in-addressbook", "last30"};
1884
1885 g_return_if_fail(node != NULL);
1886 g_return_if_fail(fp != NULL);
1887
1888 item = FOLDER_ITEM(node->data);
1889 g_return_if_fail(item != NULL);
1890
1891 depth = g_node_depth(node);
1892 for (i = 0; i < depth; i++)
1893 fputs(" ", fp);
1894 if (depth == 1) {
1895 Folder *folder = item->folder;
1896
1897 fprintf(fp, "<folder type=\"%s\"",
1898 folder_type_str[FOLDER_TYPE(folder)]);
1899 if (folder->name)
1900 PUT_ESCAPE_STR(fp, "name", folder->name);
1901 if (FOLDER_TYPE(folder) == F_MH)
1902 PUT_ESCAPE_STR(fp, "path",
1903 LOCAL_FOLDER(folder)->rootpath);
1904 if (item->collapsed && node->children)
1905 fputs(" collapsed=\"1\"", fp);
1906 if (folder->account)
1907 fprintf(fp, " account_id=\"%d\"",
1908 folder->account->account_id);
1909 else if (item->account)
1910 fprintf(fp, " account_id=\"%d\"",
1911 item->account->account_id);
1912 if (item->ac_apply_sub)
1913 fputs(" account_apply_sub=\"1\"", fp);
1914 } else {
1915 fprintf(fp, "<folderitem type=\"%s\"",
1916 folder_item_stype_str[item->stype]);
1917 if (item->name)
1918 PUT_ESCAPE_STR(fp, "name", item->name);
1919 if (item->path)
1920 PUT_ESCAPE_STR(fp, "path", item->path);
1921
1922 if (item->no_sub)
1923 fputs(" no_sub=\"1\"", fp);
1924 if (item->no_select)
1925 fputs(" no_select=\"1\"", fp);
1926 if (item->collapsed && node->children)
1927 fputs(" collapsed=\"1\"", fp);
1928 if (item->threaded)
1929 fputs(" threaded=\"1\"", fp);
1930 else
1931 fputs(" threaded=\"0\"", fp);
1932
1933 if (item->sort_key != SORT_BY_NONE) {
1934 fprintf(fp, " sort_key=\"%s\"",
1935 sort_key_str[item->sort_key]);
1936 if (item->sort_type == SORT_ASCENDING)
1937 fprintf(fp, " sort_type=\"ascending\"");
1938 else
1939 fprintf(fp, " sort_type=\"descending\"");
1940 }
1941 if (item->qsearch_cond_type > 0 &&
1942 item->qsearch_cond_type < 10) {
1943 fprintf(fp, " qsearch_cond=\"%s\"",
1944 qsearch_cond_str[item->qsearch_cond_type]);
1945 }
1946
1947 fprintf(fp,
1948 " mtime=\"%lld\" new=\"%d\" unread=\"%d\" total=\"%d\"",
1949 (gint64)item->mtime, item->new, item->unread, item->total);
1950
1951 if (item->account)
1952 fprintf(fp, " account_id=\"%d\"",
1953 item->account->account_id);
1954 if (item->ac_apply_sub)
1955 fputs(" account_apply_sub=\"1\"", fp);
1956
1957 if (item->auto_to)
1958 PUT_ESCAPE_STR(fp, "to", item->auto_to);
1959 if (item->use_auto_to_on_reply)
1960 fputs(" use_auto_to_on_reply=\"1\"", fp);
1961 if (item->auto_cc)
1962 PUT_ESCAPE_STR(fp, "cc", item->auto_cc);
1963 if (item->auto_bcc)
1964 PUT_ESCAPE_STR(fp, "bcc", item->auto_bcc);
1965 if (item->auto_replyto)
1966 PUT_ESCAPE_STR(fp, "replyto", item->auto_replyto);
1967
1968 if (item->trim_summary_subject)
1969 fputs(" trim_summary_subject=\"1\"", fp);
1970 if (item->trim_compose_subject)
1971 fputs(" trim_compose_subject=\"1\"", fp);
1972 }
1973
1974 if (node->children) {
1975 GNode *child;
1976 fputs(">\n", fp);
1977
1978 child = node->children;
1979 while (child) {
1980 GNode *cur;
1981
1982 cur = child;
1983 child = cur->next;
1984 folder_write_list_recursive(cur, data);
1985 }
1986
1987 for (i = 0; i < depth; i++)
1988 fputs(" ", fp);
1989 fprintf(fp, "</%s>\n", depth == 1 ? "folder" : "folderitem");
1990 } else
1991 fputs(" />\n", fp);
1992}
1993
1994#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __FOLDER_H__
21#define __FOLDER_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28#include <time.h>
29
30typedef struct _Folder Folder;
31typedef struct _FolderClass FolderClass;
32
33typedef struct _LocalFolder LocalFolder;
34typedef struct _RemoteFolder RemoteFolder;
35#if 0
36typedef struct _MboxFolder MboxFolder;
37typedef struct _MaildirFolder MaildirFolder;
38#endif
39
40typedef struct _FolderItem FolderItem;
41
42#define FOLDER(obj) ((Folder *)obj)
43#define FOLDER_CLASS(obj) (FOLDER(obj)->klass)
44#define FOLDER_TYPE(obj) (FOLDER(obj)->klass->type)
45
46#define LOCAL_FOLDER(obj) ((LocalFolder *)obj)
47#define REMOTE_FOLDER(obj) ((RemoteFolder *)obj)
48
49#define FOLDER_IS_LOCAL(obj) (FOLDER_TYPE(obj) == F_MH || \
50 FOLDER_TYPE(obj) == F_MBOX || \
51 FOLDER_TYPE(obj) == F_MAILDIR)
52#define FOLDER_IS_REMOTE(obj) (FOLDER_TYPE(obj) == F_IMAP || \
53 FOLDER_TYPE(obj) == F_NEWS)
54
55#if 0
56#define MBOX_FOLDER(obj) ((MboxFolder *)obj)
57#define MAILDIR_FOLDER(obj) ((MaildirFolder *)obj)
58#endif
59
60#define FOLDER_ITEM(obj) ((FolderItem *)obj)
61
62#define FOLDER_ITEM_CAN_ADD(obj) \
63 ((obj) && FOLDER_ITEM(obj)->folder && \
64 FOLDER_ITEM(obj)->path && \
65 (FOLDER_IS_LOCAL(FOLDER_ITEM(obj)->folder) || \
66 FOLDER_TYPE(FOLDER_ITEM(obj)->folder) == F_IMAP) && \
67 !FOLDER_ITEM(obj)->no_select)
68
69#define FOLDER_ITEM_IS_SENT_FOLDER(obj) ((obj) && \
70 ((obj)->stype == F_OUTBOX || \
71 (obj)->stype == F_DRAFT || \
72 (obj)->stype == F_QUEUE))
73
74typedef enum
75{
76 F_MH,
77 F_MBOX,
78 F_MAILDIR,
79 F_IMAP,
80 F_NEWS,
81 F_UNKNOWN
82} FolderType;
83
84typedef enum
85{
86 F_NORMAL,
87 F_INBOX,
88 F_OUTBOX,
89 F_DRAFT,
90 F_QUEUE,
91 F_TRASH,
92 F_JUNK,
93 F_VIRTUAL
94} SpecialFolderItemType;
95
96typedef enum
97{
98 SORT_BY_NONE,
99 SORT_BY_NUMBER,
100 SORT_BY_SIZE,
101 SORT_BY_DATE,
102 SORT_BY_TDATE,
103 SORT_BY_FROM,
104 SORT_BY_SUBJECT,
105 SORT_BY_SCORE,
106 SORT_BY_LABEL,
107 SORT_BY_MARK,
108 SORT_BY_UNREAD,
109 SORT_BY_MIME,
110 SORT_BY_TO
111} FolderSortKey;
112
113typedef enum
114{
115 SORT_ASCENDING,
116 SORT_DESCENDING
117} FolderSortType;
118
119typedef void (*FolderUIFunc) (Folder *folder,
120 FolderItem *item,
121 gpointer data);
122typedef gboolean (*FolderUIFunc2) (Folder *folder,
123 FolderItem *item,
124 guint count,
125 guint total,
126 gpointer data);
127typedef void (*FolderDestroyNotify) (Folder *folder,
128 FolderItem *item,
129 gpointer data);
130
131#include "prefs_account.h"
132#include "session.h"
133#include "procmsg.h"
134#include "utils.h"
135
136struct _Folder
137{
138 FolderClass *klass;
139
140 gchar *name;
141 PrefsAccount *account;
142
143 FolderItem *inbox;
144 FolderItem *outbox;
145 FolderItem *draft;
146 FolderItem *queue;
147 FolderItem *trash;
148
149 FolderUIFunc ui_func;
150 gpointer ui_func_data;
151
152 GNode *node;
153
154 gpointer data;
155};
156
157struct _FolderClass
158{
159 FolderType type;
160
161 /* virtual functions */
162 Folder * (*folder_new) (const gchar *name,
163 const gchar *path);
164 void (*destroy) (Folder *folder);
165
166 gint (*scan_tree) (Folder *folder);
167 gint (*create_tree) (Folder *folder);
168
169 GSList * (*get_msg_list) (Folder *folder,
170 FolderItem *item,
171 gboolean use_cache);
172 GSList * (*get_uncached_msg_list)
173 (Folder *folder,
174 FolderItem *item);
175 /* return value is filename encoding */
176 gchar * (*fetch_msg) (Folder *folder,
177 FolderItem *item,
178 gint num);
179 MsgInfo * (*get_msginfo) (Folder *folder,
180 FolderItem *item,
181 gint num);
182 gint (*add_msg) (Folder *folder,
183 FolderItem *dest,
184 const gchar *file,
185 MsgFlags *flags,
186 gboolean remove_source);
187 gint (*add_msgs) (Folder *folder,
188 FolderItem *dest,
189 GSList *file_list,
190 gboolean remove_source,
191 gint *first);
192 gint (*add_msg_msginfo) (Folder *folder,
193 FolderItem *dest,
194 MsgInfo *msginfo,
195 gboolean remove_source);
196 gint (*add_msgs_msginfo) (Folder *folder,
197 FolderItem *dest,
198 GSList *msginfo_list,
199 gboolean remove_source,
200 gint *first);
201 gint (*move_msg) (Folder *folder,
202 FolderItem *dest,
203 MsgInfo *msginfo);
204 gint (*move_msgs) (Folder *folder,
205 FolderItem *dest,
206 GSList *msglist);
207 gint (*copy_msg) (Folder *folder,
208 FolderItem *dest,
209 MsgInfo *msginfo);
210 gint (*copy_msgs) (Folder *folder,
211 FolderItem *dest,
212 GSList *msglist);
213 gint (*remove_msg) (Folder *folder,
214 FolderItem *item,
215 MsgInfo *msginfo);
216 gint (*remove_msgs) (Folder *folder,
217 FolderItem *item,
218 GSList *msglist);
219 gint (*remove_all_msg) (Folder *folder,
220 FolderItem *item);
221 gboolean (*is_msg_changed) (Folder *folder,
222 FolderItem *item,
223 MsgInfo *msginfo);
224 gint (*close) (Folder *folder,
225 FolderItem *item);
226 gint (*scan) (Folder *folder,
227 FolderItem *item);
228
229 FolderItem * (*create_folder) (Folder *folder,
230 FolderItem *parent,
231 const gchar *name);
232 gint (*rename_folder) (Folder *folder,
233 FolderItem *item,
234 const gchar *name);
235 gint (*move_folder) (Folder *folder,
236 FolderItem *item,
237 FolderItem *new_parent);
238 gint (*remove_folder) (Folder *folder,
239 FolderItem *item);
240};
241
242struct _LocalFolder
243{
244 Folder folder;
245
246 gchar *rootpath;
247};
248
249struct _RemoteFolder
250{
251 Folder folder;
252
253 Session *session;
254
255 guint remove_cache_on_destroy : 1;
256 guint dummy1 : 1;
257 guint dummy2 : 1;
258 guint dummy3 : 1;
259};
260
261#if 0
262struct _MboxFolder
263{
264 LocalFolder lfolder;
265};
266
267struct _MaildirFolder
268{
269 LocalFolder lfolder;
270};
271#endif
272
273struct _FolderItem
274{
275 SpecialFolderItemType stype;
276
277 gchar *name; /* UTF-8 */
278 gchar *path; /* UTF-8 */
279
280 stime_t mtime;
281
282 gint new;
283 gint unread;
284 gint total;
285 gint unmarked_num;
286
287 gint last_num;
288
289 /* special flags */
290 guint no_sub : 1; /* no child allowed? */
291 guint no_select : 1; /* not selectable? */
292 guint collapsed : 1; /* collapsed item */
293 guint threaded : 1; /* threaded folder view */
294
295 guint opened : 1; /* opened by summary view */
296 guint updated : 1; /* folderview should be updated */
297
298 guint cache_dirty : 1; /* cache file needs to be updated */
299 guint mark_dirty : 1; /* mark file needs to be updated */
300
301 FolderSortKey sort_key;
302 FolderSortType sort_type;
303
304 GNode *node;
305
306 FolderItem *parent;
307
308 Folder *folder;
309
310 PrefsAccount *account;
311
312 gboolean ac_apply_sub;
313
314 gchar *auto_to;
315 gboolean use_auto_to_on_reply;
316 gchar *auto_cc;
317 gchar *auto_bcc;
318 gchar *auto_replyto;
319
320 gboolean trim_summary_subject;
321 gboolean trim_compose_subject;
322
323 GSList *cache_queue;
324 GSList *mark_queue;
325
326 guint last_selected;
327 gint qsearch_cond_type;
328
329 gpointer data;
330};
331
332Folder *folder_new (FolderType type,
333 const gchar *name,
334 const gchar *path);
335void folder_local_folder_init (Folder *folder,
336 const gchar *name,
337 const gchar *path);
338void folder_remote_folder_init (Folder *folder,
339 const gchar *name,
340 const gchar *path);
341
342void folder_destroy (Folder *folder);
343void folder_local_folder_destroy (LocalFolder *lfolder);
344void folder_remote_folder_destroy(RemoteFolder *rfolder);
345
346gint folder_remote_folder_destroy_all_sessions (void);
347
348gboolean folder_remote_folder_is_session_active
349 (RemoteFolder *rfolder);
350gboolean folder_remote_folder_active_session_exist (void);
351
352gint folder_scan_tree (Folder *folder);
353gint folder_create_tree (Folder *folder);
354
355FolderItem *folder_item_new (const gchar *name,
356 const gchar *path);
357void folder_item_append (FolderItem *parent,
358 FolderItem *item);
359FolderItem *folder_item_copy (FolderItem *item);
360void folder_item_remove (FolderItem *item);
361void folder_item_remove_children (FolderItem *item);
362void folder_item_destroy (FolderItem *item);
363
364gint folder_item_compare (FolderItem *item_a,
365 FolderItem *item_b);
366
367void folder_set_ui_func (Folder *folder,
368 FolderUIFunc func,
369 gpointer data);
370void folder_set_ui_func2 (Folder *folder,
371 FolderUIFunc2 func,
372 gpointer data);
373FolderUIFunc2 folder_get_ui_func2 (Folder *folder);
374gboolean folder_call_ui_func2 (Folder *folder,
375 FolderItem *item,
376 guint count,
377 guint total);
378
379void folder_set_name (Folder *folder,
380 const gchar *name);
381void folder_tree_destroy (Folder *folder);
382
383void folder_add (Folder *folder);
384
385GList *folder_get_list (void);
386gint folder_read_list (void);
387void folder_write_list (void);
388
389gchar *folder_get_status (GPtrArray *folders,
390 gboolean full);
391
392Folder *folder_find_from_path (const gchar *path);
393Folder *folder_find_from_name (const gchar *name,
394 FolderType type);
395FolderItem *folder_find_item_from_path (const gchar *path);
396FolderItem *folder_find_child_item_by_name (FolderItem *item,
397 const gchar *name);
398gchar *folder_get_identifier (Folder *folder);
399gchar *folder_item_get_identifier (FolderItem *item);
400FolderItem *folder_find_item_from_identifier (const gchar *identifier);
401FolderItem *folder_find_item_and_num_from_id (const gchar *identifier,
402 gint *num);
403
404Folder *folder_get_default_folder (void);
405FolderItem *folder_get_default_inbox (void);
406FolderItem *folder_get_default_outbox (void);
407FolderItem *folder_get_default_draft (void);
408FolderItem *folder_get_default_queue (void);
409FolderItem *folder_get_default_trash (void);
410FolderItem *folder_get_default_junk (void);
411
412gboolean folder_item_is_trash (FolderItem *item);
413
414FolderItem *folder_get_junk (Folder *folder);
415void folder_set_junk (Folder *folder,
416 FolderItem *item);
417
418void folder_set_missing_folders (void);
419void folder_unref_account_all (PrefsAccount *account);
420
421/* return value is filename encoding */
422gchar *folder_get_path (Folder *folder);
423gchar *folder_item_get_path (FolderItem *item);
424
425gint folder_item_scan (FolderItem *item);
426void folder_item_scan_foreach (GHashTable *table);
427GSList *folder_item_get_msg_list (FolderItem *item,
428 gboolean use_cache);
429GSList *folder_item_get_uncached_msg_list
430 (FolderItem *item);
431/* return value is filename encoding */
432gchar *folder_item_fetch_msg (FolderItem *item,
433 gint num);
434gint folder_item_fetch_all_msg (FolderItem *item);
435MsgInfo *folder_item_get_msginfo (FolderItem *item,
436 gint num);
437gint folder_item_add_msg (FolderItem *dest,
438 const gchar *file,
439 MsgFlags *flags,
440 gboolean remove_source);
441gint folder_item_add_msgs (FolderItem *dest,
442 GSList *file_list,
443 gboolean remove_source,
444 gint *first);
445gint folder_item_add_msg_msginfo (FolderItem *dest,
446 MsgInfo *msginfo,
447 gboolean remove_source);
448gint folder_item_add_msgs_msginfo (FolderItem *dest,
449 GSList *msginfo_list,
450 gboolean remove_source,
451 gint *first);
452gint folder_item_move_msg (FolderItem *dest,
453 MsgInfo *msginfo);
454gint folder_item_move_msgs (FolderItem *dest,
455 GSList *msglist);
456gint folder_item_copy_msg (FolderItem *dest,
457 MsgInfo *msginfo);
458gint folder_item_copy_msgs (FolderItem *dest,
459 GSList *msglist);
460gint folder_item_remove_msg (FolderItem *item,
461 MsgInfo *msginfo);
462gint folder_item_remove_msgs (FolderItem *item,
463 GSList *msglist);
464gint folder_item_remove_all_msg (FolderItem *item);
465
466gboolean folder_item_is_msg_changed (FolderItem *item,
467 MsgInfo *msginfo);
468
469/* return value is filename encoding */
470gchar *folder_item_get_cache_file (FolderItem *item);
471gchar *folder_item_get_mark_file (FolderItem *item);
472
473gint folder_item_close (FolderItem *item);
474
475#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2011 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include <glib.h>
21#include <stdio.h>
22#include <string.h>
23#include <ctype.h>
24
25#include "html.h"
26#include "codeconv.h"
27#include "utils.h"
28
29#define HTMLBUFSIZE 8192
30#define HR_STR "------------------------------------------------"
31
32typedef struct _HTMLSymbol HTMLSymbol;
33
34struct _HTMLSymbol
35{
36 gchar *const key;
37 gchar *const val;
38};
39
40static HTMLSymbol symbol_list[] = {
41 {"&lt;" , "<"},
42 {"&gt;" , ">"},
43 {"&amp;" , "&"},
44 {"&quot;" , "\""}
45};
46
47/* &#160; - &#255; */
48static HTMLSymbol latin_symbol_list[] = {
49 {"&nbsp;" , " "},
50 /* {"&nbsp;" , "\302\240"}, */
51 {"&iexcl;" , "\302\241"},
52 {"&cent;" , "\302\242"},
53 {"&pound;" , "\302\243"},
54 {"&curren;", "\302\244"},
55 {"&yen;" , "\302\245"},
56 {"&brvbar;", "\302\246"},
57 {"&sect;" , "\302\247"},
58 {"&uml;" , "\302\250"},
59 {"&copy;" , "\302\251"},
60 {"&ordf;" , "\302\252"},
61 {"&laquo;" , "\302\253"},
62 {"&not;" , "\302\254"},
63 {"&shy;" , "\302\255"},
64 {"&reg;" , "\302\256"},
65 {"&macr;" , "\302\257"},
66 {"&deg;" , "\302\260"},
67 {"&plusm;" , "\302\261"},
68 {"&sup2;" , "\302\262"},
69 {"&sup3;" , "\302\263"},
70 {"&acute;" , "\302\264"},
71 {"&micro;" , "\302\265"},
72 {"&para;" , "\302\266"},
73 {"&middot;", "\302\267"},
74 {"&cedil;" , "\302\270"},
75 {"&sup1;" , "\302\271"},
76 {"&ordm;" , "\302\272"},
77 {"&raquo;" , "\302\273"},
78 {"&frac14;", "\302\274"},
79 {"&frac12;", "\302\275"},
80 {"&frac34;", "\302\276"},
81 {"&iquest;", "\302\277"},
82
83 {"&Agrave;", "\303\200"},
84 {"&Aacute;", "\303\201"},
85 {"&Acirc;" , "\303\202"},
86 {"&Atilde;", "\303\203"},
87 {"&Auml;" , "\303\204"},
88 {"&Aring;" , "\303\205"},
89 {"&AElig;" , "\303\206"},
90 {"&Ccedil;", "\303\207"},
91 {"&Egrave;", "\303\210"},
92 {"&Eacute;", "\303\211"},
93 {"&Ecirc;" , "\303\212"},
94 {"&Euml;" , "\303\213"},
95 {"&Igrave;", "\303\214"},
96 {"&Iacute;", "\303\215"},
97 {"&Icirc;" , "\303\216"},
98 {"&Iuml;" , "\303\217"},
99 {"&ETH;" , "\303\220"},
100 {"&Ntilde;", "\303\221"},
101 {"&Ograve;", "\303\222"},
102 {"&Oacute;", "\303\223"},
103 {"&Ocirc;" , "\303\224"},
104 {"&Otilde;", "\303\225"},
105 {"&Ouml;" , "\303\226"},
106 {"&times;" , "\303\227"},
107 {"&Oslash;", "\303\230"},
108 {"&Ugrave;", "\303\231"},
109 {"&Uacute;", "\303\232"},
110 {"&Ucirc;" , "\303\233"},
111 {"&Uuml;" , "\303\234"},
112 {"&Yacute;", "\303\235"},
113 {"&THORN;" , "\303\236"},
114 {"&szlig;" , "\303\237"},
115 {"&agrave;", "\303\240"},
116 {"&aacute;", "\303\241"},
117 {"&acirc;" , "\303\242"},
118 {"&atilde;", "\303\243"},
119 {"&auml;" , "\303\244"},
120 {"&aring;" , "\303\245"},
121 {"&aelig;" , "\303\246"},
122 {"&ccedil;", "\303\247"},
123 {"&egrave;", "\303\250"},
124 {"&eacute;", "\303\251"},
125 {"&ecirc;" , "\303\252"},
126 {"&euml;" , "\303\253"},
127 {"&igrave;", "\303\254"},
128 {"&iacute;", "\303\255"},
129 {"&icirc;" , "\303\256"},
130 {"&iuml;" , "\303\257"},
131 {"&eth;" , "\303\260"},
132 {"&ntilde;", "\303\261"},
133 {"&ograve;", "\303\262"},
134 {"&oacute;", "\303\263"},
135 {"&ocirc;" , "\303\264"},
136 {"&otilde;", "\303\265"},
137 {"&ouml;" , "\303\266"},
138 {"&divide;", "\303\267"},
139 {"&oslash;", "\303\270"},
140 {"&ugrave;", "\303\271"},
141 {"&uacute;", "\303\272"},
142 {"&ucirc;" , "\303\273"},
143 {"&uuml;" , "\303\274"},
144 {"&yacute;", "\303\275"},
145 {"&thorn;" , "\303\276"},
146 {"&yuml;" , "\303\277"}
147};
148
149static HTMLSymbol other_symbol_list[] = {
150 /* Non-standard? */
151 {"&#133;" , "..."},
152 {"&#146;" , "'"},
153 {"&#150;" , "-"},
154 {"&#153;" , "\xe2\x84\xa2"},
155 {"&#156;" , "\xc5\x93"},
156
157 /* Symbolic characters */
158 {"&trade;" , "\xe2\x84\xa2"},
159
160 /* Latin extended */
161 {"&OElig;" , "\xc5\x92"},
162 {"&oelig;" , "\xc5\x93"},
163 {"&Scaron;", "\xc5\xa0"},
164 {"&scaron;", "\xc5\xa1"},
165 {"&Yuml;" , "\xc5\xb8"},
166 {"&circ;" , "\xcb\x86"},
167 {"&tilde;" , "\xcb\x9c"},
168 {"&fnof;" , "\xc6\x92"},
169};
170
171static GHashTable *default_symbol_table;
172
173static HTMLState html_read_line (HTMLParser *parser);
174
175static void html_append_char (HTMLParser *parser,
176 gchar ch);
177static void html_append_str (HTMLParser *parser,
178 const gchar *str,
179 gint len);
180
181static gchar *html_find_char (HTMLParser *parser,
182 gchar ch);
183static gchar *html_find_str (HTMLParser *parser,
184 const gchar *str);
185static gchar *html_find_str_case (HTMLParser *parser,
186 const gchar *str);
187
188static HTMLState html_parse_tag (HTMLParser *parser);
189static void html_parse_special (HTMLParser *parser);
190static void html_get_parenthesis (HTMLParser *parser,
191 gchar *buf,
192 gint len);
193
194static gchar *html_unescape_str (HTMLParser *parser,
195 const gchar *str);
196
197
198HTMLParser *html_parser_new(FILE *fp, CodeConverter *conv)
199{
200 HTMLParser *parser;
201
202 g_return_val_if_fail(fp != NULL, NULL);
203 g_return_val_if_fail(conv != NULL, NULL);
204
205 parser = g_new0(HTMLParser, 1);
206 parser->fp = fp;
207 parser->conv = conv;
208 parser->str = g_string_new(NULL);
209 parser->buf = g_string_new(NULL);
210 parser->bufp = parser->buf->str;
211 parser->state = HTML_NORMAL;
212 parser->href = NULL;
213 parser->newline = TRUE;
214 parser->empty_line = TRUE;
215 parser->space = FALSE;
216 parser->pre = FALSE;
217 parser->blockquote = 0;
218
219#define SYMBOL_TABLE_ADD(table, list) \
220{ \
221 gint i; \
222 \
223 for (i = 0; i < sizeof(list) / sizeof(list[0]); i++) \
224 g_hash_table_insert(table, list[i].key, list[i].val); \
225}
226
227 if (!default_symbol_table) {
228 default_symbol_table =
229 g_hash_table_new(g_str_hash, g_str_equal);
230 SYMBOL_TABLE_ADD(default_symbol_table, symbol_list);
231 SYMBOL_TABLE_ADD(default_symbol_table, latin_symbol_list);
232 SYMBOL_TABLE_ADD(default_symbol_table, other_symbol_list);
233 }
234
235#undef SYMBOL_TABLE_ADD
236
237 parser->symbol_table = default_symbol_table;
238
239 return parser;
240}
241
242void html_parser_destroy(HTMLParser *parser)
243{
244 g_string_free(parser->str, TRUE);
245 g_string_free(parser->buf, TRUE);
246 g_free(parser->href);
247 g_free(parser);
248}
249
250const gchar *html_parse(HTMLParser *parser)
251{
252 parser->state = HTML_NORMAL;
253 g_string_truncate(parser->str, 0);
254
255 if (*parser->bufp == '\0') {
256 g_string_truncate(parser->buf, 0);
257 parser->bufp = parser->buf->str;
258 if (html_read_line(parser) == HTML_EOF)
259 return NULL;
260 }
261
262 while (*parser->bufp != '\0') {
263 switch (*parser->bufp) {
264 case '<':
265 if (parser->str->len == 0)
266 html_parse_tag(parser);
267 else
268 return parser->str->str;
269 break;
270 case '&':
271 html_parse_special(parser);
272 break;
273 case ' ':
274 case '\t':
275 case '\r':
276 case '\n':
277 if (parser->bufp[0] == '\r' && parser->bufp[1] == '\n')
278 parser->bufp++;
279
280 if (!parser->pre) {
281 if (!parser->newline)
282 parser->space = TRUE;
283
284 parser->bufp++;
285 break;
286 }
287 /* fallthrough */
288 default:
289 html_append_char(parser, *parser->bufp++);
290 }
291 }
292
293 return parser->str->str;
294}
295
296static HTMLState html_read_line(HTMLParser *parser)
297{
298 gchar buf[HTMLBUFSIZE];
299 gchar *conv_str;
300 gint index;
301
302 if (fgets(buf, sizeof(buf), parser->fp) == NULL) {
303 parser->state = HTML_EOF;
304 return HTML_EOF;
305 }
306
307 conv_str = conv_convert(parser->conv, buf);
308 if (!conv_str) {
309 index = parser->bufp - parser->buf->str;
310
311 conv_str = conv_utf8todisp(buf, NULL);
312 g_string_append(parser->buf, conv_str);
313 g_free(conv_str);
314
315 parser->bufp = parser->buf->str + index;
316
317 return HTML_CONV_FAILED;
318 }
319
320 index = parser->bufp - parser->buf->str;
321
322 g_string_append(parser->buf, conv_str);
323 g_free(conv_str);
324
325 parser->bufp = parser->buf->str + index;
326
327 return HTML_NORMAL;
328}
329
330static void html_append_char(HTMLParser *parser, gchar ch)
331{
332 GString *str = parser->str;
333 const gchar *bq_prefix = NULL;
334
335 if (!parser->pre && parser->space) {
336 g_string_append_c(str, ' ');
337 parser->space = FALSE;
338 }
339
340 if (parser->newline && parser->blockquote > 0)
341 bq_prefix = " ";
342
343 parser->empty_line = FALSE;
344 if (ch == '\n') {
345 parser->newline = TRUE;
346 if (str->len > 0 && str->str[str->len - 1] == '\n')
347 parser->empty_line = TRUE;
348 } else
349 parser->newline = FALSE;
350
351 if (bq_prefix) {
352 gint i;
353 for (i = 0; i < parser->blockquote; i++)
354 g_string_append(str, bq_prefix);
355 }
356 g_string_append_c(str, ch);
357}
358
359static void html_append_str(HTMLParser *parser, const gchar *str, gint len)
360{
361 GString *string = parser->str;
362 const gchar *bq_prefix = NULL;
363
364 if (!parser->pre && parser->space) {
365 g_string_append_c(string, ' ');
366 parser->space = FALSE;
367 }
368
369 if (len == 0) return;
370
371 if (parser->newline && parser->blockquote > 0)
372 bq_prefix = " ";
373
374 if (bq_prefix) {
375 gint i;
376 for (i = 0; i < parser->blockquote; i++)
377 g_string_append(string, bq_prefix);
378 }
379
380 if (len < 0)
381 g_string_append(string, str);
382 else
383 g_string_append_len(string, str, len);
384
385 parser->empty_line = FALSE;
386 if (string->len > 0 && string->str[string->len - 1] == '\n') {
387 parser->newline = TRUE;
388 if (string->len > 1 && string->str[string->len - 2] == '\n')
389 parser->empty_line = TRUE;
390 } else
391 parser->newline = FALSE;
392}
393
394static gchar *html_find_char(HTMLParser *parser, gchar ch)
395{
396 gchar *p;
397
398 while ((p = strchr(parser->bufp, ch)) == NULL) {
399 if (html_read_line(parser) == HTML_EOF)
400 return NULL;
401 }
402
403 return p;
404}
405
406static gchar *html_find_str(HTMLParser *parser, const gchar *str)
407{
408 gchar *p;
409
410 while ((p = strstr(parser->bufp, str)) == NULL) {
411 if (html_read_line(parser) == HTML_EOF)
412 return NULL;
413 }
414
415 return p;
416}
417
418static gchar *html_find_str_case(HTMLParser *parser, const gchar *str)
419{
420 gchar *p;
421
422 while ((p = strcasestr(parser->bufp, str)) == NULL) {
423 if (html_read_line(parser) == HTML_EOF)
424 return NULL;
425 }
426
427 return p;
428}
429
430static HTMLTag *html_get_tag(const gchar *str)
431{
432 HTMLTag *tag;
433 gchar *tmp;
434 gchar *tmpp;
435
436 g_return_val_if_fail(str != NULL, NULL);
437
438 if (*str == '\0' || *str == '!') return NULL;
439
440 tmp = g_strdup(str);
441
442 tag = g_new0(HTMLTag, 1);
443
444 for (tmpp = tmp; *tmpp != '\0' && !g_ascii_isspace(*tmpp); tmpp++) {
445 if (tmpp > tmp && *tmpp == '/') {
446 *tmpp = '\0';
447 break;
448 }
449 }
450
451 if (*tmpp == '\0') {
452 g_strdown(tmp);
453 tag->name = tmp;
454 return tag;
455 } else {
456 *tmpp++ = '\0';
457 g_strdown(tmp);
458 tag->name = g_strdup(tmp);
459 }
460
461 while (*tmpp != '\0') {
462 HTMLAttr *attr;
463 gchar *attr_name;
464 gchar *attr_value;
465 gchar *p;
466 gchar quote;
467
468 while (g_ascii_isspace(*tmpp)) tmpp++;
469 if (tmpp > tmp && *tmpp == '/')
470 break;
471 attr_name = tmpp;
472
473 while (*tmpp != '\0' && !g_ascii_isspace(*tmpp) &&
474 *tmpp != '=')
475 tmpp++;
476 if (*tmpp != '\0' && *tmpp != '=') {
477 *tmpp++ = '\0';
478 while (g_ascii_isspace(*tmpp)) tmpp++;
479 }
480
481 if (*tmpp == '=') {
482 *tmpp++ = '\0';
483 while (g_ascii_isspace(*tmpp)) tmpp++;
484
485 if (*tmpp == '"' || *tmpp == '\'') {
486 /* name="value" */
487 quote = *tmpp;
488 tmpp++;
489 attr_value = tmpp;
490 if ((p = strchr(attr_value, quote)) == NULL) {
491 g_warning("html_get_tag(): syntax error in tag: '%s'\n", str);
492 break;
493 }
494 tmpp = p;
495 *tmpp++ = '\0';
496 while (g_ascii_isspace(*tmpp)) tmpp++;
497 } else {
498 /* name=value */
499 attr_value = tmpp;
500 while (*tmpp != '\0' && !g_ascii_isspace(*tmpp)) tmpp++;
501 if (*tmpp != '\0')
502 *tmpp++ = '\0';
503 }
504 } else
505 attr_value = "";
506
507 g_strchomp(attr_name);
508 g_strdown(attr_name);
509 attr = g_new(HTMLAttr, 1);
510 attr->name = g_strdup(attr_name);
511 attr->value = g_strdup(attr_value);
512 tag->attr = g_list_append(tag->attr, attr);
513 }
514
515 g_free(tmp);
516
517 return tag;
518}
519
520static void html_free_tag(HTMLTag *tag)
521{
522 if (!tag) return;
523
524 g_free(tag->name);
525 while (tag->attr != NULL) {
526 HTMLAttr *attr = (HTMLAttr *)tag->attr->data;
527 g_free(attr->name);
528 g_free(attr->value);
529 g_free(attr);
530 tag->attr = g_list_remove(tag->attr, tag->attr->data);
531 }
532 g_free(tag);
533}
534
535static HTMLState html_parse_tag(HTMLParser *parser)
536{
537 gchar buf[HTMLBUFSIZE];
538 HTMLTag *tag;
539
540 html_get_parenthesis(parser, buf, sizeof(buf));
541
542 tag = html_get_tag(buf);
543
544 parser->state = HTML_UNKNOWN;
545 if (!tag) return HTML_UNKNOWN;
546
547 if (!strcmp(tag->name, "br")) {
548 parser->space = FALSE;
549 html_append_char(parser, '\n');
550 parser->state = HTML_BR;
551 } else if (!strcmp(tag->name, "a")) {
552 GList *cur;
553
554 for (cur = tag->attr; cur != NULL; cur = cur->next) {
555 HTMLAttr *attr = (HTMLAttr *)cur->data;
556
557 if (attr && !strcmp(attr->name, "href")) {
558 g_free(parser->href);
559 parser->href = html_unescape_str(parser, attr->value);
560 parser->state = HTML_HREF;
561 break;
562 }
563 }
564 } else if (!strcmp(tag->name, "/a")) {
565 g_free(parser->href);
566 parser->href = NULL;
567 parser->state = HTML_NORMAL;
568 } else if (!strcmp(tag->name, "p")) {
569 parser->space = FALSE;
570 if (!parser->empty_line) {
571 parser->space = FALSE;
572 if (!parser->newline) html_append_char(parser, '\n');
573 html_append_char(parser, '\n');
574 }
575 parser->state = HTML_PAR;
576 } else if (!strcmp(tag->name, "pre")) {
577 parser->pre = TRUE;
578 parser->state = HTML_PRE;
579 } else if (!strcmp(tag->name, "/pre")) {
580 parser->pre = FALSE;
581 parser->state = HTML_NORMAL;
582 } else if (!strcmp(tag->name, "blockquote")) {
583 parser->blockquote++;
584 parser->state = HTML_BLOCKQUOTE;
585 } else if (!strcmp(tag->name, "/blockquote")) {
586 parser->blockquote--;
587 if (parser->blockquote < 0)
588 parser->blockquote = 0;
589 parser->state = HTML_NORMAL;
590 } else if (!strcmp(tag->name, "hr")) {
591 if (!parser->newline) {
592 parser->space = FALSE;
593 html_append_char(parser, '\n');
594 }
595 html_append_str(parser, HR_STR "\n", -1);
596 parser->state = HTML_HR;
597 } else if (!strcmp(tag->name, "div") ||
598 !strcmp(tag->name, "ul") ||
599 !strcmp(tag->name, "li") ||
600 !strcmp(tag->name, "table") ||
601 !strcmp(tag->name, "tr") ||
602 (tag->name[0] == 'h' && g_ascii_isdigit(tag->name[1]))) {
603 if (!parser->newline) {
604 parser->space = FALSE;
605 html_append_char(parser, '\n');
606 }
607 parser->state = HTML_NORMAL;
608 } else if (!strcmp(tag->name, "/table") ||
609 (tag->name[0] == '/' &&
610 tag->name[1] == 'h' &&
611 g_ascii_isdigit(tag->name[1]))) {
612 if (!parser->empty_line) {
613 parser->space = FALSE;
614 if (!parser->newline) html_append_char(parser, '\n');
615 html_append_char(parser, '\n');
616 }
617 parser->state = HTML_NORMAL;
618 } else if (!strcmp(tag->name, "/div") ||
619 !strcmp(tag->name, "/ul") ||
620 !strcmp(tag->name, "/li")) {
621 if (!parser->newline) {
622 parser->space = FALSE;
623 html_append_char(parser, '\n');
624 }
625 parser->state = HTML_NORMAL;
626 }
627
628 html_free_tag(tag);
629
630 return parser->state;
631}
632
633static void html_parse_special(HTMLParser *parser)
634{
635 gchar symbol_name[9];
636 gint n;
637 const gchar *val;
638
639 parser->state = HTML_UNKNOWN;
640 g_return_if_fail(*parser->bufp == '&');
641
642 /* &foo; */
643 for (n = 0; parser->bufp[n] != '\0' && parser->bufp[n] != ';'; n++)
644 ;
645 if (n > 7 || parser->bufp[n] != ';') {
646 /* output literal `&' */
647 html_append_char(parser, *parser->bufp++);
648 parser->state = HTML_NORMAL;
649 return;
650 }
651 strncpy2(symbol_name, parser->bufp, n + 2);
652 parser->bufp += n + 1;
653
654 if ((val = g_hash_table_lookup(parser->symbol_table, symbol_name))
655 != NULL) {
656 html_append_str(parser, val, -1);
657 parser->state = HTML_NORMAL;
658 return;
659 } else if (symbol_name[1] == '#' && g_ascii_isdigit(symbol_name[2])) {
660 gint ch;
661
662 ch = atoi(symbol_name + 2);
663 if (ch < 128 && g_ascii_isprint(ch)) {
664 html_append_char(parser, ch);
665 parser->state = HTML_NORMAL;
666 return;
667 } else {
668 /* ISO 10646 to UTF-8 */
669 gchar buf[6];
670 gint len;
671
672 len = g_unichar_to_utf8((gunichar)ch, buf);
673 if (len > 0) {
674 html_append_str(parser, buf, len);
675 parser->state = HTML_NORMAL;
676 return;
677 }
678 }
679 }
680
681 html_append_str(parser, symbol_name, -1);
682}
683
684static void html_get_parenthesis(HTMLParser *parser, gchar *buf, gint len)
685{
686 gchar *p;
687
688 buf[0] = '\0';
689 g_return_if_fail(*parser->bufp == '<');
690
691 /* ignore comment / CSS / script stuff */
692 if (!strncmp(parser->bufp, "<!--", 4)) {
693 parser->bufp += 4;
694 if ((p = html_find_str(parser, "-->")) != NULL)
695 parser->bufp = p + 3;
696 return;
697 }
698 if (!g_ascii_strncasecmp(parser->bufp, "<style", 6)) {
699 parser->bufp += 6;
700 if ((p = html_find_str_case(parser, "</style")) != NULL) {
701 parser->bufp = p + 7;
702 if ((p = html_find_char(parser, '>')) != NULL)
703 parser->bufp = p + 1;
704 }
705 return;
706 }
707 if (!g_ascii_strncasecmp(parser->bufp, "<script", 7)) {
708 parser->bufp += 7;
709 if ((p = html_find_str_case(parser, "</script")) != NULL) {
710 parser->bufp = p + 8;
711 if ((p = html_find_char(parser, '>')) != NULL)
712 parser->bufp = p + 1;
713 }
714 return;
715 }
716
717 parser->bufp++;
718 if ((p = html_find_char(parser, '>')) == NULL)
719 return;
720
721 strncpy2(buf, parser->bufp, MIN(p - parser->bufp + 1, len));
722 g_strstrip(buf);
723 parser->bufp = p + 1;
724}
725
726static gchar *html_unescape_str(HTMLParser *parser, const gchar *str)
727{
728 const gchar *p = str;
729 gchar symbol_name[9];
730 gint n;
731 const gchar *val;
732 gchar *unescape_str;
733 gchar *up;
734
735 if (!str)
736 return NULL;
737
738 up = unescape_str = g_malloc(strlen(str) + 1);
739
740 while (*p != '\0') {
741 switch (*p) {
742 case '&':
743 for (n = 0; p[n] != '\0' && p[n] != ';'; n++)
744 ;
745 if (n > 7 || p[n] != ';') {
746 *up++ = *p++;
747 break;
748 }
749 strncpy2(symbol_name, p, n + 2);
750 p += n + 1;
751
752 if ((val = g_hash_table_lookup(parser->symbol_table, symbol_name)) != NULL) {
753 gint len = strlen(val);
754 if (len <= n + 1) {
755 strcpy(up, val);
756 up += len;
757 } else {
758 strcpy(up, symbol_name);
759 up += n + 1;
760 }
761 } else if (symbol_name[1] == '#' && g_ascii_isdigit(symbol_name[2])) {
762 gint ch;
763
764 ch = atoi(symbol_name + 2);
765 if (ch < 128 && g_ascii_isprint(ch)) {
766 *up++ = ch;
767 } else {
768 /* ISO 10646 to UTF-8 */
769 gchar buf[6];
770 gint len;
771
772 len = g_unichar_to_utf8((gunichar)ch, buf);
773 if (len > 0 && len <= 6 && len <= n + 1) {
774 memcpy(up, buf, len);
775 up += len;
776 } else {
777 strcpy(up, symbol_name);
778 up += n + 1;
779 }
780 }
781 }
782
783 break;
784 default:
785 *up++ = *p++;
786 }
787 }
788
789 *up = '\0';
790 return unescape_str;
791}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __HTML_H__
21#define __HTML_H__
22
23#include <glib.h>
24#include <stdio.h>
25
26#include "codeconv.h"
27
28typedef enum
29{
30 HTML_NORMAL,
31 HTML_PAR,
32 HTML_BR,
33 HTML_HR,
34 HTML_HREF,
35 HTML_IMG,
36 HTML_FONT,
37 HTML_PRE,
38 HTML_UNKNOWN,
39 HTML_CONV_FAILED,
40 HTML_ERR,
41 HTML_EOF,
42 HTML_BLOCKQUOTE
43} HTMLState;
44
45typedef struct _HTMLParser HTMLParser;
46typedef struct _HTMLAttr HTMLAttr;
47typedef struct _HTMLTag HTMLTag;
48
49struct _HTMLParser
50{
51 FILE *fp;
52 CodeConverter *conv;
53
54 GHashTable *symbol_table;
55
56 GString *str;
57 GString *buf;
58
59 gchar *bufp;
60
61 HTMLState state;
62
63 gchar *href;
64
65 gboolean newline;
66 gboolean empty_line;
67 gboolean space;
68 gboolean pre;
69 gint blockquote;
70};
71
72struct _HTMLAttr
73{
74 gchar *name;
75 gchar *value;
76};
77
78struct _HTMLTag
79{
80 gchar *name;
81 GList *attr;
82};
83
84HTMLParser *html_parser_new (FILE *fp,
85 CodeConverter *conv);
86void html_parser_destroy (HTMLParser *parser);
87const gchar *html_parse (HTMLParser *parser);
88
89#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <stdio.h>
29#include <string.h>
30#include <stdlib.h>
31#include <dirent.h>
32#include <unistd.h>
33#include <ctype.h>
34#include <time.h>
35#if HAVE_ICONV
36# include <iconv.h>
37#endif
38
39#include "sylmain.h"
40#include "imap.h"
41#include "socket.h"
42#include "socks.h"
43#include "ssl.h"
44#include "recv.h"
45#include "procmsg.h"
46#include "procheader.h"
47#include "folder.h"
48#include "prefs_account.h"
49#include "codeconv.h"
50#include "md5_hmac.h"
51#include "base64.h"
52#include "utils.h"
53#include "prefs_common.h"
54#include "virtual.h"
55
56#define IMAP4_PORT 143
57#if USE_SSL
58#define IMAPS_PORT 993
59#endif
60
61#define IMAP_COPY_LIMIT 200
62#define IMAP_CMD_LIMIT 1000
63
64#define QUOTE_IF_REQUIRED(out, str) \
65{ \
66 if (!str || *str == '\0') { \
67 Xstrdup_a(out, "\"\"", return IMAP_ERROR); \
68 } else if (strpbrk(str, " \t(){}[]%&*\"\\") != NULL) { \
69 gchar *__tmp; \
70 gint len; \
71 const gchar *p; \
72 gchar *tp; \
73 \
74 len = strlen(str) * 2 + 3; \
75 Xalloca(__tmp, len, return IMAP_ERROR); \
76 tp = __tmp; \
77 *tp++ = '\"'; \
78 for (p = str; *p != '\0'; p++) { \
79 if (*p == '\"' || *p == '\\') \
80 *tp++ = '\\'; \
81 *tp++ = *p; \
82 } \
83 *tp++ = '\"'; \
84 *tp = '\0'; \
85 out = __tmp; \
86 } else { \
87 Xstrdup_a(out, str, return IMAP_ERROR); \
88 } \
89}
90
91typedef gint (*IMAPThreadFunc) (IMAPSession *session,
92 gpointer data);
93typedef gint (*IMAPProgressFunc) (IMAPSession *session,
94 gint count,
95 gint total,
96 gpointer data);
97
98typedef struct _IMAPRealSession
99{
100 IMAPSession imap_session;
101#if USE_THREADS
102 GThreadPool *pool;
103 IMAPThreadFunc thread_func;
104 gpointer thread_data;
105 gboolean is_running;
106 gint prog_count;
107 gint prog_total;
108 gint flag;
109 gint retval;
110#endif
111} IMAPRealSession;
112
113static GList *session_list = NULL;
114
115static void imap_folder_init (Folder *folder,
116 const gchar *name,
117 const gchar *path);
118
119static Folder *imap_folder_new (const gchar *name,
120 const gchar *path);
121static void imap_folder_destroy (Folder *folder);
122
123static Session *imap_session_new (PrefsAccount *account);
124static gint imap_session_connect (IMAPSession *session);
125static gint imap_session_reconnect (IMAPSession *session);
126static void imap_session_destroy (Session *session);
127/* static void imap_session_destroy_all (void); */
128
129static gint imap_search_flags (IMAPSession *session,
130 GArray **uids,
131 GHashTable **flags_table);
132static gint imap_fetch_flags (IMAPSession *session,
133 GArray **uids,
134 GHashTable **flags_table);
135
136static GSList *imap_get_msg_list (Folder *folder,
137 FolderItem *item,
138 gboolean use_cache);
139static GSList *imap_get_uncached_msg_list
140 (Folder *folder,
141 FolderItem *item);
142static gchar *imap_fetch_msg (Folder *folder,
143 FolderItem *item,
144 gint uid);
145static MsgInfo *imap_get_msginfo (Folder *folder,
146 FolderItem *item,
147 gint uid);
148static gint imap_add_msg (Folder *folder,
149 FolderItem *dest,
150 const gchar *file,
151 MsgFlags *flags,
152 gboolean remove_source);
153static gint imap_add_msgs (Folder *folder,
154 FolderItem *dest,
155 GSList *file_list,
156 gboolean remove_source,
157 gint *first);
158static gint imap_add_msg_msginfo (Folder *folder,
159 FolderItem *dest,
160 MsgInfo *msginfo,
161 gboolean remove_source);
162static gint imap_add_msgs_msginfo (Folder *folder,
163 FolderItem *dest,
164 GSList *msglist,
165 gboolean remove_source,
166 gint *first);
167
168static gint imap_move_msg (Folder *folder,
169 FolderItem *dest,
170 MsgInfo *msginfo);
171static gint imap_move_msgs (Folder *folder,
172 FolderItem *dest,
173 GSList *msglist);
174static gint imap_copy_msg (Folder *folder,
175 FolderItem *dest,
176 MsgInfo *msginfo);
177static gint imap_copy_msgs (Folder *folder,
178 FolderItem *dest,
179 GSList *msglist);
180
181static gint imap_remove_msg (Folder *folder,
182 FolderItem *item,
183 MsgInfo *msginfo);
184static gint imap_remove_msgs (Folder *folder,
185 FolderItem *item,
186 GSList *msglist);
187static gint imap_remove_all_msg (Folder *folder,
188 FolderItem *item);
189
190static gboolean imap_is_msg_changed (Folder *folder,
191 FolderItem *item,
192 MsgInfo *msginfo);
193
194static gint imap_close (Folder *folder,
195 FolderItem *item);
196
197static gint imap_scan_folder (Folder *folder,
198 FolderItem *item);
199static gint imap_scan_tree (Folder *folder);
200
201static gint imap_create_tree (Folder *folder);
202
203static FolderItem *imap_create_folder (Folder *folder,
204 FolderItem *parent,
205 const gchar *name);
206static gint imap_rename_folder (Folder *folder,
207 FolderItem *item,
208 const gchar *name);
209static gint imap_move_folder (Folder *folder,
210 FolderItem *item,
211 FolderItem *new_parent);
212static gint imap_remove_folder (Folder *folder,
213 FolderItem *item);
214
215static IMAPSession *imap_session_get (Folder *folder);
216
217static gint imap_greeting (IMAPSession *session);
218static gint imap_auth (IMAPSession *session,
219 const gchar *user,
220 const gchar *pass,
221 IMAPAuthType type);
222
223static gint imap_scan_tree_recursive (IMAPSession *session,
224 FolderItem *item,
225 GSList *item_list);
226static GSList *imap_get_folder_list (IMAPSession *session,
227 FolderItem *item);
228static GSList *imap_parse_list (IMAPSession *session,
229 const gchar *real_path,
230 gchar *separator);
231static GSList *imap_add_inter_folders (GSList *item_list,
232 const gchar *root_path);
233static GSList *imap_get_part_folder_list(GSList *item_list,
234 FolderItem *item);
235
236static void imap_create_missing_folders (Folder *folder);
237static FolderItem *imap_create_special_folder
238 (Folder *folder,
239 SpecialFolderItemType stype,
240 const gchar *name);
241
242static gint imap_do_copy_msgs (Folder *folder,
243 FolderItem *dest,
244 GSList *msglist,
245 gboolean remove_source);
246static gint imap_remove_msgs_by_seq_set (Folder *folder,
247 FolderItem *item,
248 GSList *seq_list);
249
250static GSList *imap_get_uncached_messages (IMAPSession *session,
251 FolderItem *item,
252 guint32 first_uid,
253 guint32 last_uid,
254 gint exists,
255 gboolean update_count);
256static void imap_delete_cached_message (FolderItem *item,
257 guint32 uid);
258static GSList *imap_delete_cached_messages (GSList *mlist,
259 FolderItem *item,
260 guint32 first_uid,
261 guint32 last_uid);
262static void imap_delete_all_cached_messages (FolderItem *item);
263
264#if USE_SSL
265static SockInfo *imap_open (const gchar *server,
266 gushort port,
267 SocksInfo *socks_info,
268 SSLType ssl_type);
269#else
270static SockInfo *imap_open (const gchar *server,
271 gushort port,
272 SocksInfo *socks_info);
273#endif
274
275static gint imap_msg_list_change_perm_flags (GSList *msglist,
276 MsgPermFlags flags,
277 gboolean is_set);
278static gchar *imap_get_flag_str (IMAPFlags flags);
279static gint imap_set_message_flags (IMAPSession *session,
280 const gchar *seq_set,
281 IMAPFlags flags,
282 gboolean is_set);
283static gint imap_select (IMAPSession *session,
284 IMAPFolder *folder,
285 const gchar *path,
286 gint *exists,
287 gint *recent,
288 gint *unseen,
289 guint32 *uid_validity);
290static gint imap_status (IMAPSession *session,
291 IMAPFolder *folder,
292 const gchar *path,
293 gint *messages,
294 gint *recent,
295 guint32 *uid_next,
296 guint32 *uid_validity,
297 gint *unseen);
298
299static void imap_parse_namespace (IMAPSession *session,
300 IMAPFolder *folder);
301static void imap_get_namespace_by_list (IMAPSession *session,
302 IMAPFolder *folder);
303static IMAPNameSpace *imap_find_namespace (IMAPFolder *folder,
304 const gchar *path);
305static gchar imap_get_path_separator (IMAPFolder *folder,
306 const gchar *path);
307static gchar *imap_get_real_path (IMAPFolder *folder,
308 const gchar *path);
309
310static gchar *imap_parse_atom (IMAPSession *session,
311 gchar *src,
312 gchar *dest,
313 gint dest_len,
314 GString *str);
315static MsgFlags imap_parse_flags (const gchar *flag_str);
316static IMAPFlags imap_parse_imap_flags (const gchar *flag_str);
317static MsgInfo *imap_parse_envelope (IMAPSession *session,
318 FolderItem *item,
319 GString *line_str);
320
321static gboolean imap_has_capability (IMAPSession *session,
322 const gchar *capability);
323static void imap_capability_free (IMAPSession *session);
324
325/* low-level IMAP4rev1 commands */
326static gint imap_cmd_capability (IMAPSession *session);
327static gint imap_cmd_authenticate
328 (IMAPSession *session,
329 const gchar *user,
330 const gchar *pass,
331 IMAPAuthType type);
332static gint imap_cmd_login (IMAPSession *session,
333 const gchar *user,
334 const gchar *pass);
335static gint imap_cmd_logout (IMAPSession *session);
336static gint imap_cmd_noop (IMAPSession *session);
337#if USE_SSL
338static gint imap_cmd_starttls (IMAPSession *session);
339#endif
340static gint imap_cmd_namespace (IMAPSession *session,
341 gchar **ns_str);
342static gint imap_cmd_list (IMAPSession *session,
343 const gchar *ref,
344 const gchar *mailbox,
345 GPtrArray *argbuf);
346static gint imap_cmd_do_select (IMAPSession *session,
347 const gchar *folder,
348 gboolean examine,
349 gint *exists,
350 gint *recent,
351 gint *unseen,
352 guint32 *uid_validity);
353static gint imap_cmd_select (IMAPSession *session,
354 const gchar *folder,
355 gint *exists,
356 gint *recent,
357 gint *unseen,
358 guint32 *uid_validity);
359static gint imap_cmd_examine (IMAPSession *session,
360 const gchar *folder,
361 gint *exists,
362 gint *recent,
363 gint *unseen,
364 guint32 *uid_validity);
365static gint imap_cmd_create (IMAPSession *session,
366 const gchar *folder);
367static gint imap_cmd_rename (IMAPSession *session,
368 const gchar *oldfolder,
369 const gchar *newfolder);
370static gint imap_cmd_delete (IMAPSession *session,
371 const gchar *folder);
372static gint imap_cmd_subscribe (IMAPSession *session,
373 const gchar *folder);
374static gint imap_cmd_envelope (IMAPSession *session,
375 const gchar *seq_set);
376static gint imap_cmd_search (IMAPSession *session,
377 const gchar *criteria,
378 GArray **result);
379static gint imap_cmd_fetch (IMAPSession *session,
380 guint32 uid,
381 const gchar *filename);
382static gint imap_cmd_append (IMAPSession *session,
383 const gchar *destfolder,
384 const gchar *file,
385 IMAPFlags flags,
386 guint32 *new_uid);
387static gint imap_cmd_copy (IMAPSession *session,
388 const gchar *seq_set,
389 const gchar *destfolder);
390static gint imap_cmd_store (IMAPSession *session,
391 const gchar *seq_set,
392 const gchar *sub_cmd);
393static gint imap_cmd_expunge (IMAPSession *session);
394static gint imap_cmd_close (IMAPSession *session);
395
396static gint imap_cmd_ok (IMAPSession *session,
397 GPtrArray *argbuf);
398static gint imap_cmd_ok_real (IMAPSession *session,
399 GPtrArray *argbuf);
400static gint imap_cmd_gen_send (IMAPSession *session,
401 const gchar *format, ...);
402static gint imap_cmd_gen_recv (IMAPSession *session,
403 gchar **ret);
404
405static gint imap_cmd_gen_recv_silent (IMAPSession *session,
406 gchar **ret);
407
408/* misc utility functions */
409static gchar *strchr_cpy (const gchar *src,
410 gchar ch,
411 gchar *dest,
412 gint len);
413static gchar *get_quoted (const gchar *src,
414 gchar ch,
415 gchar *dest,
416 gint len);
417static gchar *search_array_contain_str (GPtrArray *array,
418 gchar *str);
419static gchar *search_array_str (GPtrArray *array,
420 gchar *str);
421static void imap_path_separator_subst (gchar *str,
422 gchar separator);
423
424static gchar *imap_modified_utf7_to_utf8 (const gchar *mutf7_str);
425static gchar *imap_utf8_to_modified_utf7 (const gchar *from);
426
427static GSList *imap_get_seq_set_from_msglist (GSList *msglist,
428 gint limit);
429static gint imap_seq_set_get_count (const gchar *seq_set);
430static void imap_seq_set_free (GSList *seq_list);
431
432static GHashTable *imap_get_uid_table (GArray *array);
433
434static gboolean imap_rename_folder_func (GNode *node,
435 gpointer data);
436
437#if USE_THREADS
438static gint imap_thread_run (IMAPSession *session,
439 IMAPThreadFunc func,
440 gpointer data);
441static gint imap_thread_run_progress (IMAPSession *session,
442 IMAPThreadFunc func,
443 IMAPProgressFunc progress_func,
444 gpointer data);
445#endif
446
447static FolderClass imap_class =
448{
449 F_IMAP,
450
451 imap_folder_new,
452 imap_folder_destroy,
453
454 imap_scan_tree,
455 imap_create_tree,
456
457 imap_get_msg_list,
458 imap_get_uncached_msg_list,
459 imap_fetch_msg,
460 imap_get_msginfo,
461 imap_add_msg,
462 imap_add_msgs,
463 imap_add_msg_msginfo,
464 imap_add_msgs_msginfo,
465 imap_move_msg,
466 imap_move_msgs,
467 imap_copy_msg,
468 imap_copy_msgs,
469 imap_remove_msg,
470 imap_remove_msgs,
471 imap_remove_all_msg,
472 imap_is_msg_changed,
473 imap_close,
474 imap_scan_folder,
475
476 imap_create_folder,
477 imap_rename_folder,
478 imap_move_folder,
479 imap_remove_folder
480};
481
482
483FolderClass *imap_get_class(void)
484{
485 return &imap_class;
486}
487
488static Folder *imap_folder_new(const gchar *name, const gchar *path)
489{
490 Folder *folder;
491
492 folder = (Folder *)g_new0(IMAPFolder, 1);
493 imap_folder_init(folder, name, path);
494
495 return folder;
496}
497
498static void imap_folder_destroy(Folder *folder)
499{
500 g_return_if_fail(folder->account != NULL);
501
502 if (REMOTE_FOLDER(folder)->remove_cache_on_destroy) {
503 gchar *dir;
504 gchar *server;
505
506 dir = folder_get_path(folder);
507 if (is_dir_exist(dir))
508 remove_dir_recursive(dir);
509 g_free(dir);
510
511 server = uriencode_for_filename(folder->account->recv_server);
512 dir = g_strconcat(get_imap_cache_dir(), G_DIR_SEPARATOR_S,
513 server, NULL);
514 if (is_dir_exist(dir))
515 g_rmdir(dir);
516 g_free(dir);
517 g_free(server);
518 }
519
520 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
521}
522
523static void imap_folder_init(Folder *folder, const gchar *name,
524 const gchar *path)
525{
526 folder->klass = imap_get_class();
527 folder_remote_folder_init(folder, name, path);
528}
529
530static IMAPSession *imap_session_get(Folder *folder)
531{
532 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
533 gint ret;
534
535 g_return_val_if_fail(folder != NULL, NULL);
536 g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, NULL);
537 g_return_val_if_fail(folder->account != NULL, NULL);
538
539 if (!prefs_common.online_mode)
540 return NULL;
541
542 if (!rfolder->session) {
543 rfolder->session = imap_session_new(folder->account);
544 if (rfolder->session)
545 imap_parse_namespace(IMAP_SESSION(rfolder->session),
546 IMAP_FOLDER(folder));
547 return IMAP_SESSION(rfolder->session);
548 }
549
550 if (imap_is_session_active(IMAP_FOLDER(folder))) {
551 g_warning("imap_session_get: session is busy.");
552 return NULL;
553 }
554
555 if (time(NULL) - rfolder->session->last_access_time <
556 SESSION_TIMEOUT_INTERVAL) {
557 return IMAP_SESSION(rfolder->session);
558 }
559
560 if ((ret = imap_cmd_noop(IMAP_SESSION(rfolder->session))) != IMAP_SUCCESS) {
561 if (ret == IMAP_EAGAIN) {
562 g_warning("imap_session_get: session is busy.");
563 return NULL;
564 }
565
566 log_warning(_("IMAP4 connection to %s has been"
567 " disconnected. Reconnecting...\n"),
568 folder->account->recv_server);
569 if (imap_session_reconnect(IMAP_SESSION(rfolder->session))
570 == IMAP_SUCCESS)
571 imap_parse_namespace(IMAP_SESSION(rfolder->session),
572 IMAP_FOLDER(folder));
573 else {
574 session_destroy(rfolder->session);
575 rfolder->session = NULL;
576 }
577 }
578
579 return IMAP_SESSION(rfolder->session);
580}
581
582static gint imap_greeting(IMAPSession *session)
583{
584 gchar *greeting;
585 gint ok;
586
587 if ((ok = imap_cmd_gen_recv(session, &greeting)) != IMAP_SUCCESS) {
588 log_warning("Cannot get greeting message (%d)\n", ok);
589 return ok;
590 }
591
592 if (greeting[0] != '*' || greeting[1] != ' ')
593 ok = IMAP_ERROR;
594 else if (!strncmp(greeting + 2, "OK", 2))
595 ok = IMAP_SUCCESS;
596 else if (!strncmp(greeting + 2, "PREAUTH", 7)) {
597 session->authenticated = TRUE;
598 ok = IMAP_SUCCESS;
599 } else
600 ok = IMAP_ERROR;
601
602 g_free(greeting);
603 return ok;
604}
605
606static gint imap_auth(IMAPSession *session, const gchar *user,
607 const gchar *pass, IMAPAuthType type)
608{
609 gboolean nologin;
610 gint ok = IMAP_AUTHFAIL;
611
612 nologin = imap_has_capability(session, "LOGINDISABLED");
613
614 switch (type) {
615 case 0:
616 if (imap_has_capability(session, "AUTH=CRAM-MD5"))
617 ok = imap_cmd_authenticate(session, user, pass,
618 IMAP_AUTH_CRAM_MD5);
619 else if (imap_has_capability(session, "AUTH=PLAIN"))
620 ok = imap_cmd_authenticate(session, user, pass,
621 IMAP_AUTH_PLAIN);
622 else if (nologin)
623 log_print(_("IMAP4 server disables LOGIN.\n"));
624 else
625 ok = imap_cmd_login(session, user, pass);
626 break;
627 case IMAP_AUTH_LOGIN:
628 if (nologin)
629 log_warning(_("IMAP4 server disables LOGIN.\n"));
630 else
631 ok = imap_cmd_login(session, user, pass);
632 break;
633 case IMAP_AUTH_CRAM_MD5:
634 case IMAP_AUTH_PLAIN:
635 ok = imap_cmd_authenticate(session, user, pass, type);
636 break;
637 default:
638 break;
639 }
640
641 if (ok == IMAP_SUCCESS)
642 session->authenticated = TRUE;
643
644 return ok;
645}
646
647static Session *imap_session_new(PrefsAccount *account)
648{
649 IMAPSession *session;
650 gushort port;
651
652 g_return_val_if_fail(account != NULL, NULL);
653 g_return_val_if_fail(account->recv_server != NULL, NULL);
654 g_return_val_if_fail(account->userid != NULL, NULL);
655
656#if USE_SSL
657 port = account->set_imapport ? account->imapport
658 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
659#else
660 port = account->set_imapport ? account->imapport : IMAP4_PORT;
661#endif
662
663 session = IMAP_SESSION(g_new0(IMAPRealSession, 1));
664
665 session_init(SESSION(session));
666
667 SESSION(session)->type = SESSION_IMAP;
668 SESSION(session)->sock = NULL;
669 SESSION(session)->server = g_strdup(account->recv_server);
670 SESSION(session)->port = port;
671#if USE_SSL
672 SESSION(session)->ssl_type = account->ssl_imap;
673#endif
674 SESSION(session)->last_access_time = time(NULL);
675 SESSION(session)->data = account;
676
677 SESSION(session)->destroy = imap_session_destroy;
678
679 session->authenticated = FALSE;
680 session->capability = NULL;
681 session->uidplus = FALSE;
682 session->mbox = NULL;
683 session->cmd_count = 0;
684
685 session_list = g_list_append(session_list, session);
686
687 if (imap_session_connect(session) != IMAP_SUCCESS) {
688 log_warning(_("Could not establish IMAP connection.\n"));
689 session_destroy(SESSION(session));
690 return NULL;
691 }
692
693 return SESSION(session);
694}
695
696static gint imap_session_connect(IMAPSession *session)
697{
698 SockInfo *sock;
699 SocksInfo *socks_info = NULL;
700 PrefsAccount *account;
701 const gchar *pass;
702
703 g_return_val_if_fail(session != NULL, IMAP_ERROR);
704
705 account = (PrefsAccount *)(SESSION(session)->data);
706
707 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
708 SESSION(session)->server, SESSION(session)->port);
709
710 pass = account->passwd;
711 if (!pass)
712 pass = account->tmp_pass;
713 if (!pass) {
714 gchar *tmp_pass;
715
716 tmp_pass = input_query_password(account->recv_server,
717 account->userid);
718 if (!tmp_pass)
719 return IMAP_ERROR;
720
721 account->tmp_pass = tmp_pass;
722 pass = account->tmp_pass;
723 }
724
725 if (account->use_socks && account->use_socks_for_recv &&
726 account->proxy_host) {
727 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);
728 }
729
730#if USE_SSL
731 if ((sock = imap_open(SESSION(session)->server, SESSION(session)->port,
732 socks_info, SESSION(session)->ssl_type)) == NULL)
733#else
734 if ((sock = imap_open(SESSION(session)->server, SESSION(session)->port,
735 socks_info))
736 == NULL)
737#endif
738 return IMAP_ERROR;
739
740 if (socks_info)
741 socks_info_free(socks_info);
742
743 SESSION(session)->sock = sock;
744
745 if (imap_greeting(session) != IMAP_SUCCESS)
746 return IMAP_ERROR;
747 if (imap_cmd_capability(session) != IMAP_SUCCESS)
748 return IMAP_ERROR;
749
750 if (imap_has_capability(session, "UIDPLUS"))
751 session->uidplus = TRUE;
752
753#if USE_SSL
754 if (account->ssl_imap == SSL_STARTTLS &&
755 imap_has_capability(session, "STARTTLS")) {
756 gint ok;
757
758 ok = imap_cmd_starttls(session);
759 if (ok != IMAP_SUCCESS) {
760 log_warning(_("Can't start TLS session.\n"));
761 return IMAP_ERROR;
762 }
763 if (!ssl_init_socket_with_method(sock, SSL_METHOD_TLSv1))
764 return IMAP_SOCKET;
765
766 /* capability can be changed after STARTTLS */
767 if (imap_cmd_capability(session) != IMAP_SUCCESS)
768 return IMAP_ERROR;
769 }
770#endif
771
772 if (!session->authenticated &&
773 imap_auth(session, account->userid, pass, account->imap_auth_type)
774 != IMAP_SUCCESS) {
775 if (account->tmp_pass) {
776 g_free(account->tmp_pass);
777 account->tmp_pass = NULL;
778 }
779 imap_cmd_logout(session);
780 return IMAP_AUTHFAIL;
781 }
782
783 return IMAP_SUCCESS;
784}
785
786static gint imap_session_reconnect(IMAPSession *session)
787{
788 g_return_val_if_fail(session != NULL, IMAP_ERROR);
789
790 session_disconnect(SESSION(session));
791
792 imap_capability_free(session);
793 session->uidplus = FALSE;
794 g_free(session->mbox);
795 session->mbox = NULL;
796 session->authenticated = FALSE;
797 SESSION(session)->state = SESSION_READY;
798
799 return imap_session_connect(session);
800}
801
802static void imap_session_destroy(Session *session)
803{
804#if USE_THREADS
805 IMAPRealSession *real = (IMAPRealSession *)session;
806
807 if (real->pool)
808 g_thread_pool_free(real->pool, TRUE, TRUE);
809#endif
810 imap_capability_free(IMAP_SESSION(session));
811 g_free(IMAP_SESSION(session)->mbox);
812 session_list = g_list_remove(session_list, session);
813}
814
815#if 0
816static void imap_session_destroy_all(void)
817{
818 while (session_list != NULL) {
819 IMAPSession *session = (IMAPSession *)session_list->data;
820
821 imap_cmd_logout(session);
822 session_destroy(SESSION(session));
823 }
824}
825#endif
826
827#define THROW goto catch
828
829static gint imap_search_flags(IMAPSession *session, GArray **uids,
830 GHashTable **flags_table)
831{
832 gint ok;
833 gint i;
834 GArray *flag_uids;
835 GHashTable *unseen_table;
836 GHashTable *flagged_table;
837 GHashTable *answered_table;
838 guint32 uid;
839 IMAPFlags flags;
840
841 ok = imap_cmd_search(session, "ALL", uids);
842 if (ok != IMAP_SUCCESS) return ok;
843
844 ok = imap_cmd_search(session, "UNSEEN", &flag_uids);
845 if (ok != IMAP_SUCCESS) {
846 g_array_free(*uids, TRUE);
847 return ok;
848 }
849 unseen_table = imap_get_uid_table(flag_uids);
850 g_array_free(flag_uids, TRUE);
851 ok = imap_cmd_search(session, "FLAGGED", &flag_uids);
852 if (ok != IMAP_SUCCESS) {
853 g_hash_table_destroy(unseen_table);
854 g_array_free(*uids, TRUE);
855 return ok;
856 }
857 flagged_table = imap_get_uid_table(flag_uids);
858 g_array_free(flag_uids, TRUE);
859 ok = imap_cmd_search(session, "ANSWERED", &flag_uids);
860 if (ok != IMAP_SUCCESS) {
861 g_hash_table_destroy(flagged_table);
862 g_hash_table_destroy(unseen_table);
863 g_array_free(*uids, TRUE);
864 return ok;
865 }
866 answered_table = imap_get_uid_table(flag_uids);
867 g_array_free(flag_uids, TRUE);
868
869 *flags_table = g_hash_table_new(NULL, g_direct_equal);
870
871 for (i = 0; i < (*uids)->len; i++) {
872 uid = g_array_index(*uids, guint32, i);
873 flags = IMAP_FLAG_DRAFT;
874 if (!g_hash_table_lookup(unseen_table, GUINT_TO_POINTER(uid)))
875 flags |= IMAP_FLAG_SEEN;
876 if (g_hash_table_lookup(flagged_table, GUINT_TO_POINTER(uid)))
877 flags |= IMAP_FLAG_FLAGGED;
878 if (g_hash_table_lookup(answered_table, GUINT_TO_POINTER(uid)))
879 flags |= IMAP_FLAG_ANSWERED;
880 g_hash_table_insert(*flags_table, GUINT_TO_POINTER(uid),
881 GINT_TO_POINTER(flags));
882 }
883
884 g_hash_table_destroy(answered_table);
885 g_hash_table_destroy(flagged_table);
886 g_hash_table_destroy(unseen_table);
887
888 return IMAP_SUCCESS;
889}
890
891static gint imap_fetch_flags(IMAPSession *session, GArray **uids,
892 GHashTable **flags_table)
893{
894 gint ok;
895 gchar *tmp;
896 gchar *cur_pos;
897 gchar buf[IMAPBUFSIZE];
898 guint32 uid;
899 IMAPFlags flags;
900
901 if (imap_cmd_gen_send(session, "UID FETCH 1:* (UID FLAGS)") != IMAP_SUCCESS)
902 return IMAP_ERROR;
903
904 *uids = g_array_new(FALSE, FALSE, sizeof(guint32));
905 *flags_table = g_hash_table_new(NULL, g_direct_equal);
906
907 log_print("IMAP4< %s\n", _("(retrieving FLAGS...)"));
908
909 while ((ok = imap_cmd_gen_recv_silent(session, &tmp)) == IMAP_SUCCESS) {
910 if (tmp[0] != '*' || tmp[1] != ' ') {
911 log_print("IMAP4< %s\n", tmp);
912 g_free(tmp);
913 break;
914 }
915 cur_pos = tmp + 2;
916
917#define PARSE_ONE_ELEMENT(ch) \
918{ \
919 cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
920 if (cur_pos == NULL) { \
921 g_warning("cur_pos == NULL\n"); \
922 g_free(tmp); \
923 g_hash_table_destroy(*flags_table); \
924 g_array_free(*uids, TRUE); \
925 return IMAP_ERROR; \
926 } \
927}
928
929 PARSE_ONE_ELEMENT(' ');
930 PARSE_ONE_ELEMENT(' ');
931 if (strcmp(buf, "FETCH") != 0) {
932 g_free(tmp);
933 continue;
934 }
935 if (*cur_pos != '(') {
936 g_free(tmp);
937 continue;
938 }
939 cur_pos++;
940 uid = 0;
941 flags = 0;
942
943 while (*cur_pos != '\0' && *cur_pos != ')') {
944 while (*cur_pos == ' ') cur_pos++;
945
946 if (!strncmp(cur_pos, "UID ", 4)) {
947 cur_pos += 4;
948 uid = strtoul(cur_pos, &cur_pos, 10);
949 } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
950 cur_pos += 6;
951 if (*cur_pos != '(') {
952 g_warning("*cur_pos != '('\n");
953 break;
954 }
955 cur_pos++;
956 PARSE_ONE_ELEMENT(')');
957 flags = imap_parse_imap_flags(buf);
958 flags |= IMAP_FLAG_DRAFT;
959 } else {
960 g_warning("invalid FETCH response: %s\n", cur_pos);
961 break;
962 }
963 }
964
965#undef PARSE_ONE_ELEMENT
966
967 if (uid > 0) {
968 g_array_append_val(*uids, uid);
969 g_hash_table_insert(*flags_table, GUINT_TO_POINTER(uid),
970 GINT_TO_POINTER(flags));
971 }
972
973 g_free(tmp);
974 }
975
976 if (ok != IMAP_SUCCESS) {
977 g_hash_table_destroy(*flags_table);
978 g_array_free(*uids, TRUE);
979 }
980
981 return ok;
982}
983
984static GSList *imap_get_msg_list_full(Folder *folder, FolderItem *item,
985 gboolean use_cache,
986 gboolean uncached_only)
987{
988 GSList *mlist = NULL;
989 IMAPSession *session;
990 gint ok, exists = 0, recent = 0, unseen = 0;
991 guint32 uid_validity = 0;
992 guint32 first_uid = 0, last_uid = 0;
993 GSList *newlist = NULL;
994
995 g_return_val_if_fail(folder != NULL, NULL);
996 g_return_val_if_fail(item != NULL, NULL);
997 g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, NULL);
998 g_return_val_if_fail(folder->account != NULL, NULL);
999
1000 item->new = item->unread = item->total = 0;
1001
1002 session = imap_session_get(folder);
1003
1004 if (!session) {
1005 if (uncached_only)
1006 return NULL;
1007 mlist = procmsg_read_cache(item, FALSE);
1008 item->last_num = procmsg_get_last_num_in_msg_list(mlist);
1009 procmsg_set_flags(mlist, item);
1010 return mlist;
1011 }
1012
1013 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1014 &exists, &recent, &unseen, &uid_validity);
1015 if (ok != IMAP_SUCCESS) THROW;
1016
1017 if (exists == 0) {
1018 imap_delete_all_cached_messages(item);
1019 return NULL;
1020 }
1021
1022 /* invalidate current cache if UIDVALIDITY has been changed */
1023 if (item->mtime != uid_validity) {
1024 debug_print("imap_get_msg_list: "
1025 "UIDVALIDITY has been changed.\n");
1026 use_cache = FALSE;
1027 }
1028
1029 if (use_cache) {
1030 GArray *uids;
1031 GHashTable *msg_table;
1032 GHashTable *flags_table;
1033 guint32 cache_last;
1034 guint32 begin = 0;
1035 GSList *cur, *next = NULL;
1036 MsgInfo *msginfo;
1037 IMAPFlags imap_flags;
1038 guint color;
1039
1040 /* get cache data */
1041 mlist = procmsg_read_cache(item, FALSE);
1042 procmsg_set_flags(mlist, item);
1043 cache_last = procmsg_get_last_num_in_msg_list(mlist);
1044
1045 /* get all UID list and flags */
1046#if 0
1047 ok = imap_search_flags(session, &uids, &flags_table);
1048 if (ok != IMAP_SUCCESS) {
1049 if (ok == IMAP_SOCKET || ok == IMAP_IOERR) THROW;
1050 ok = imap_fetch_flags(session, &uids, &flags_table);
1051 if (ok != IMAP_SUCCESS) THROW;
1052 }
1053#else
1054 ok = imap_fetch_flags(session, &uids, &flags_table);
1055 if (ok != IMAP_SUCCESS) THROW;
1056#endif
1057
1058 if (uids->len > 0) {
1059 first_uid = g_array_index(uids, guint32, 0);
1060 last_uid = g_array_index(uids, guint32, uids->len - 1);
1061 } else {
1062 g_array_free(uids, TRUE);
1063 g_hash_table_destroy(flags_table);
1064 THROW;
1065 }
1066
1067 /* sync message flags with server */
1068 for (cur = mlist; cur != NULL; cur = next) {
1069 msginfo = (MsgInfo *)cur->data;
1070 next = cur->next;
1071 imap_flags = GPOINTER_TO_INT(g_hash_table_lookup
1072 (flags_table,
1073 GUINT_TO_POINTER(msginfo->msgnum)));
1074
1075 if (imap_flags == 0) {
1076 debug_print("imap_get_msg_list: "
1077 "message %u has been deleted.\n",
1078 msginfo->msgnum);
1079 imap_delete_cached_message
1080 (item, msginfo->msgnum);
1081 if (MSG_IS_NEW(msginfo->flags))
1082 item->new--;
1083 if (MSG_IS_UNREAD(msginfo->flags))
1084 item->unread--;
1085 item->total--;
1086 mlist = g_slist_remove(mlist, msginfo);
1087 procmsg_msginfo_free(msginfo);
1088 item->cache_dirty = TRUE;
1089 item->mark_dirty = TRUE;
1090 continue;
1091 }
1092
1093 if (!IMAP_IS_SEEN(imap_flags)) {
1094 if (!MSG_IS_UNREAD(msginfo->flags)) {
1095 item->unread++;
1096 MSG_SET_PERM_FLAGS(msginfo->flags,
1097 MSG_UNREAD);
1098 item->mark_dirty = TRUE;
1099 }
1100 } else {
1101 if (MSG_IS_NEW(msginfo->flags)) {
1102 item->new--;
1103 item->mark_dirty = TRUE;
1104 }
1105 if (MSG_IS_UNREAD(msginfo->flags)) {
1106 item->unread--;
1107 item->mark_dirty = TRUE;
1108 }
1109 MSG_UNSET_PERM_FLAGS(msginfo->flags,
1110 MSG_NEW|MSG_UNREAD);
1111 }
1112
1113 if (IMAP_IS_FLAGGED(imap_flags)) {
1114 if (!MSG_IS_MARKED(msginfo->flags)) {
1115 MSG_SET_PERM_FLAGS(msginfo->flags,
1116 MSG_MARKED);
1117 item->mark_dirty = TRUE;
1118 }
1119 } else {
1120 if (MSG_IS_MARKED(msginfo->flags)) {
1121 MSG_UNSET_PERM_FLAGS(msginfo->flags,
1122 MSG_MARKED);
1123 item->mark_dirty = TRUE;
1124 }
1125 }
1126 if (IMAP_IS_ANSWERED(imap_flags)) {
1127 if (!MSG_IS_REPLIED(msginfo->flags)) {
1128 MSG_SET_PERM_FLAGS(msginfo->flags,
1129 MSG_REPLIED);
1130 item->mark_dirty = TRUE;
1131 }
1132 } else {
1133 if (MSG_IS_REPLIED(msginfo->flags)) {
1134 MSG_UNSET_PERM_FLAGS(msginfo->flags,
1135 MSG_REPLIED);
1136 item->mark_dirty = TRUE;
1137 }
1138 }
1139
1140 color = IMAP_GET_COLORLABEL_VALUE(imap_flags);
1141 if (MSG_GET_COLORLABEL_VALUE(msginfo->flags) != color) {
1142 MSG_UNSET_PERM_FLAGS(msginfo->flags,
1143 MSG_CLABEL_FLAG_MASK);
1144 MSG_SET_COLORLABEL_VALUE(msginfo->flags, color);
1145 item->mark_dirty = TRUE;
1146 }
1147 }
1148
1149 /* check for the first new message */
1150 msg_table = procmsg_msg_hash_table_create(mlist);
1151 if (msg_table == NULL)
1152 begin = first_uid;
1153 else {
1154 gint i;
1155
1156 for (i = 0; i < uids->len; i++) {
1157 guint32 uid;
1158
1159 uid = g_array_index(uids, guint32, i);
1160 if (g_hash_table_lookup
1161 (msg_table, GUINT_TO_POINTER(uid))
1162 == NULL) {
1163 debug_print("imap_get_msg_list: "
1164 "first new UID: %u\n", uid);
1165 begin = uid;
1166 break;
1167 }
1168 }
1169 g_hash_table_destroy(msg_table);
1170 }
1171
1172 g_array_free(uids, TRUE);
1173 g_hash_table_destroy(flags_table);
1174
1175 /* remove ununsed caches */
1176 if (first_uid > 0 && last_uid > 0) {
1177 mlist = imap_delete_cached_messages
1178 (mlist, item, 0, first_uid - 1);
1179 mlist = imap_delete_cached_messages
1180 (mlist, item, begin > 0 ? begin : last_uid + 1,
1181 UINT_MAX);
1182 }
1183
1184 if (begin > 0 && begin <= last_uid) {
1185 newlist = imap_get_uncached_messages
1186 (session, item, begin, last_uid,
1187 exists - item->total, TRUE);
1188 if (newlist) {
1189 item->cache_dirty = TRUE;
1190 item->mark_dirty = TRUE;
1191 }
1192 mlist = g_slist_concat(mlist, newlist);
1193 }
1194 } else {
1195 imap_delete_all_cached_messages(item);
1196 mlist = imap_get_uncached_messages(session, item, 0, 0, exists,
1197 TRUE);
1198 last_uid = procmsg_get_last_num_in_msg_list(mlist);
1199 item->cache_dirty = TRUE;
1200 item->mark_dirty = TRUE;
1201 newlist = mlist;
1202 }
1203
1204 if (!uncached_only)
1205 mlist = procmsg_sort_msg_list(mlist, item->sort_key,
1206 item->sort_type);
1207
1208 item->last_num = last_uid;
1209
1210 if (item->mark_queue)
1211 item->mark_dirty = TRUE;
1212
1213 debug_print("cache_dirty: %d, mark_dirty: %d\n",
1214 item->cache_dirty, item->mark_dirty);
1215
1216 if (!item->opened) {
1217 item->mtime = uid_validity;
1218 if (item->cache_dirty)
1219 procmsg_write_cache_list(item, mlist);
1220 if (item->mark_dirty)
1221 procmsg_write_flags_list(item, mlist);
1222 }
1223
1224catch:
1225 if (uncached_only) {
1226 GSList *cur;
1227
1228 if (newlist == NULL) {
1229 procmsg_msg_list_free(mlist);
1230 return NULL;
1231 }
1232 if (mlist == newlist)
1233 return newlist;
1234 for (cur = mlist; cur != NULL; cur = cur->next) {
1235 if (cur->next == newlist) {
1236 cur->next = NULL;
1237 procmsg_msg_list_free(mlist);
1238 return newlist;
1239 }
1240 }
1241 procmsg_msg_list_free(mlist);
1242 return NULL;
1243 }
1244
1245 return mlist;
1246}
1247
1248#undef THROW
1249
1250static GSList *imap_get_msg_list(Folder *folder, FolderItem *item,
1251 gboolean use_cache)
1252{
1253 return imap_get_msg_list_full(folder, item, use_cache, FALSE);
1254}
1255
1256static GSList *imap_get_uncached_msg_list(Folder *folder, FolderItem *item)
1257{
1258 return imap_get_msg_list_full(folder, item, TRUE, TRUE);
1259}
1260
1261static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
1262{
1263 gchar *path, *filename;
1264 IMAPSession *session;
1265 gchar nstr[16];
1266 guint32 uid32 = (guint32)uid;
1267 gint ok;
1268
1269 g_return_val_if_fail(folder != NULL, NULL);
1270 g_return_val_if_fail(item != NULL, NULL);
1271
1272 path = folder_item_get_path(item);
1273 if (!is_dir_exist(path))
1274 make_dir_hier(path);
1275 g_snprintf(nstr, sizeof(nstr), "%u", uid32);
1276 filename = g_strconcat(path, G_DIR_SEPARATOR_S, nstr, NULL);
1277 g_free(path);
1278
1279 if (is_file_exist(filename) && get_file_size(filename) > 0) {
1280 debug_print("message %u has been already cached.\n", uid32);
1281 return filename;
1282 }
1283
1284 session = imap_session_get(folder);
1285 if (!session) {
1286 g_free(filename);
1287 return NULL;
1288 }
1289
1290 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1291 NULL, NULL, NULL, NULL);
1292 if (ok != IMAP_SUCCESS) {
1293 g_warning("can't select mailbox %s\n", item->path);
1294 g_free(filename);
1295 return NULL;
1296 }
1297
1298 status_print(_("Getting message %u"), uid32);
1299 debug_print("getting message %u...\n", uid32);
1300 ok = imap_cmd_fetch(session, uid32, filename);
1301
1302 if (ok != IMAP_SUCCESS) {
1303 g_warning("can't fetch message %u\n", uid32);
1304 g_free(filename);
1305 return NULL;
1306 }
1307
1308 return filename;
1309}
1310
1311static MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
1312{
1313 IMAPSession *session;
1314 GSList *list;
1315 MsgInfo *msginfo = NULL;
1316 gint ok;
1317
1318 g_return_val_if_fail(folder != NULL, NULL);
1319 g_return_val_if_fail(item != NULL, NULL);
1320
1321 session = imap_session_get(folder);
1322 g_return_val_if_fail(session != NULL, NULL);
1323
1324 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1325 NULL, NULL, NULL, NULL);
1326 if (ok != IMAP_SUCCESS)
1327 return NULL;
1328
1329 list = imap_get_uncached_messages(session, item, uid, uid, 0, FALSE);
1330 if (list) {
1331 msginfo = (MsgInfo *)list->data;
1332 list->data = NULL;
1333 }
1334 procmsg_msg_list_free(list);
1335
1336 return msginfo;
1337}
1338
1339static gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
1340 MsgFlags *flags, gboolean remove_source)
1341{
1342 GSList file_list;
1343 MsgFileInfo fileinfo;
1344
1345 g_return_val_if_fail(file != NULL, -1);
1346
1347 fileinfo.file = (gchar *)file;
1348 fileinfo.flags = flags;
1349 file_list.data = &fileinfo;
1350 file_list.next = NULL;
1351
1352 return imap_add_msgs(folder, dest, &file_list, remove_source, NULL);
1353}
1354
1355static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
1356 gboolean remove_source, gint *first)
1357{
1358 gchar *destdir;
1359 IMAPSession *session;
1360 gint messages, recent, unseen;
1361 guint32 uid_next, uid_validity;
1362 guint32 last_uid = 0;
1363 GSList *cur;
1364 MsgFileInfo *fileinfo;
1365 gint count = 1;
1366 gint total;
1367 gint ok;
1368 GTimeVal tv_prev, tv_cur;
1369
1370 g_return_val_if_fail(folder != NULL, -1);
1371 g_return_val_if_fail(dest != NULL, -1);
1372 g_return_val_if_fail(file_list != NULL, -1);
1373
1374 session = imap_session_get(folder);
1375 if (!session) return -1;
1376
1377 g_get_current_time(&tv_prev);
1378 ui_update();
1379
1380 ok = imap_status(session, IMAP_FOLDER(folder), dest->path,
1381 &messages, &recent, &uid_next, &uid_validity, &unseen);
1382 if (ok != IMAP_SUCCESS) {
1383 g_warning("can't append messages\n");
1384 return -1;
1385 }
1386
1387 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1388
1389 if (!session->uidplus)
1390 last_uid = uid_next - 1;
1391 if (first)
1392 *first = uid_next;
1393
1394 total = g_slist_length(file_list);
1395
1396 for (cur = file_list; cur != NULL; cur = cur->next) {
1397 IMAPFlags iflags = 0;
1398 guint32 new_uid = 0;
1399
1400 fileinfo = (MsgFileInfo *)cur->data;
1401
1402 if (fileinfo->flags) {
1403 if (MSG_IS_MARKED(*fileinfo->flags))
1404 iflags |= IMAP_FLAG_FLAGGED;
1405 if (MSG_IS_REPLIED(*fileinfo->flags))
1406 iflags |= IMAP_FLAG_ANSWERED;
1407 if (!MSG_IS_UNREAD(*fileinfo->flags))
1408 iflags |= IMAP_FLAG_SEEN;
1409 }
1410
1411 if (dest->stype == F_OUTBOX ||
1412 dest->stype == F_QUEUE ||
1413 dest->stype == F_DRAFT)
1414 iflags |= IMAP_FLAG_SEEN;
1415
1416 g_get_current_time(&tv_cur);
1417 if (tv_cur.tv_sec > tv_prev.tv_sec ||
1418 tv_cur.tv_usec - tv_prev.tv_usec >
1419 PROGRESS_UPDATE_INTERVAL * 1000) {
1420 status_print(_("Appending messages to %s (%d / %d)"),
1421 dest->path, count, total);
1422 progress_show(count, total);
1423 ui_update();
1424 tv_prev = tv_cur;
1425 }
1426 ++count;
1427
1428 ok = imap_cmd_append(session, destdir, fileinfo->file, iflags,
1429 &new_uid);
1430
1431 if (ok != IMAP_SUCCESS) {
1432 g_warning("can't append message %s\n", fileinfo->file);
1433 g_free(destdir);
1434 progress_show(0, 0);
1435 return -1;
1436 }
1437
1438 if (syl_app_get())
1439 g_signal_emit_by_name(syl_app_get(), "add-msg", dest, fileinfo->file, new_uid);
1440
1441 if (!session->uidplus)
1442 last_uid++;
1443 else if (last_uid < new_uid)
1444 last_uid = new_uid;
1445
1446 dest->last_num = last_uid;
1447 dest->total++;
1448 dest->updated = TRUE;
1449
1450 if (fileinfo->flags) {
1451 if (MSG_IS_UNREAD(*fileinfo->flags))
1452 dest->unread++;
1453 } else
1454 dest->unread++;
1455 }
1456
1457 progress_show(0, 0);
1458 g_free(destdir);
1459
1460 if (remove_source) {
1461 for (cur = file_list; cur != NULL; cur = cur->next) {
1462 fileinfo = (MsgFileInfo *)cur->data;
1463 if (g_unlink(fileinfo->file) < 0)
1464 FILE_OP_ERROR(fileinfo->file, "unlink");
1465 }
1466 }
1467
1468 return last_uid;
1469}
1470
1471static gint imap_add_msg_msginfo(Folder *folder, FolderItem *dest,
1472 MsgInfo *msginfo, gboolean remove_source)
1473{
1474 GSList msglist;
1475
1476 g_return_val_if_fail(msginfo != NULL, -1);
1477
1478 msglist.data = msginfo;
1479 msglist.next = NULL;
1480
1481 return imap_add_msgs_msginfo(folder, dest, &msglist, remove_source,
1482 NULL);
1483}
1484
1485static gint imap_add_msgs_msginfo(Folder *folder, FolderItem *dest,
1486 GSList *msglist, gboolean remove_source,
1487 gint *first)
1488{
1489 GSList *file_list;
1490 gint ret;
1491
1492 file_list = procmsg_get_message_file_list(msglist);
1493 g_return_val_if_fail(file_list != NULL, -1);
1494
1495 ret = imap_add_msgs(folder, dest, file_list, remove_source, first);
1496
1497 procmsg_message_file_list_free(file_list);
1498
1499 return ret;
1500}
1501
1502static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist,
1503 gboolean remove_source)
1504{
1505 FolderItem *src;
1506 gchar *destdir;
1507 GSList *seq_list, *cur;
1508 MsgInfo *msginfo;
1509 IMAPSession *session;
1510 gint count = 0, total;
1511 gint ok = IMAP_SUCCESS;
1512
1513 g_return_val_if_fail(folder != NULL, -1);
1514 g_return_val_if_fail(dest != NULL, -1);
1515 g_return_val_if_fail(msglist != NULL, -1);
1516
1517 session = imap_session_get(folder);
1518 if (!session) return -1;
1519
1520 ui_update();
1521
1522 msginfo = (MsgInfo *)msglist->data;
1523
1524 src = msginfo->folder;
1525 if (src == dest) {
1526 g_warning("the src folder is identical to the dest.\n");
1527 return -1;
1528 }
1529
1530 ok = imap_select(session, IMAP_FOLDER(folder), src->path,
1531 NULL, NULL, NULL, NULL);
1532 if (ok != IMAP_SUCCESS)
1533 return ok;
1534
1535 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1536
1537 total = g_slist_length(msglist);
1538 seq_list = imap_get_seq_set_from_msglist(msglist, IMAP_COPY_LIMIT);
1539
1540 for (cur = seq_list; cur != NULL; cur = cur->next) {
1541 gchar *seq_set = (gchar *)cur->data;
1542
1543 count += imap_seq_set_get_count(seq_set);
1544
1545 if (remove_source) {
1546 status_print(_("Moving messages %s to %s ..."),
1547 seq_set, dest->path);
1548 debug_print("Moving message %s/[%s] to %s ...\n",
1549 src->path, seq_set, dest->path);
1550 } else {
1551 status_print(_("Copying messages %s to %s ..."),
1552 seq_set, dest->path);
1553 debug_print("Copying message %s/[%s] to %s ...\n",
1554 src->path, seq_set, dest->path);
1555 }
1556 progress_show(count, total);
1557 ui_update();
1558
1559 ok = imap_cmd_copy(session, seq_set, destdir);
1560 if (ok != IMAP_SUCCESS) {
1561 imap_seq_set_free(seq_list);
1562 progress_show(0, 0);
1563 return -1;
1564 }
1565 }
1566
1567 progress_show(0, 0);
1568
1569 dest->updated = TRUE;
1570
1571 imap_seq_set_free(seq_list);
1572 g_free(destdir);
1573
1574 for (cur = msglist; cur != NULL; cur = cur->next) {
1575 msginfo = (MsgInfo *)cur->data;
1576
1577 if (syl_app_get())
1578 g_signal_emit_by_name(syl_app_get(), "add-msg", dest, NULL, 0);
1579
1580 dest->total++;
1581 if (MSG_IS_NEW(msginfo->flags))
1582 dest->new++;
1583 if (MSG_IS_UNREAD(msginfo->flags))
1584 dest->unread++;
1585 }
1586
1587 if (remove_source) {
1588 ok = imap_remove_msgs(folder, src, msglist);
1589 if (ok != IMAP_SUCCESS)
1590 return ok;
1591 }
1592
1593 if (ok == IMAP_SUCCESS)
1594 return 0;
1595 else
1596 return -1;
1597}
1598
1599static gint imap_move_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
1600{
1601 GSList msglist;
1602
1603 g_return_val_if_fail(msginfo != NULL, -1);
1604
1605 msglist.data = msginfo;
1606 msglist.next = NULL;
1607
1608 return imap_move_msgs(folder, dest, &msglist);
1609}
1610
1611static gint imap_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
1612{
1613 MsgInfo *msginfo;
1614 GSList *file_list;
1615 gint ret = 0;
1616
1617 g_return_val_if_fail(folder != NULL, -1);
1618 g_return_val_if_fail(dest != NULL, -1);
1619 g_return_val_if_fail(msglist != NULL, -1);
1620
1621 msginfo = (MsgInfo *)msglist->data;
1622 g_return_val_if_fail(msginfo->folder != NULL, -1);
1623
1624 if (folder == msginfo->folder->folder)
1625 return imap_do_copy_msgs(folder, dest, msglist, TRUE);
1626
1627 file_list = procmsg_get_message_file_list(msglist);
1628 g_return_val_if_fail(file_list != NULL, -1);
1629
1630 ret = imap_add_msgs(folder, dest, file_list, FALSE, NULL);
1631
1632 procmsg_message_file_list_free(file_list);
1633
1634 if (ret != -1)
1635 ret = folder_item_remove_msgs(msginfo->folder, msglist);
1636
1637 return ret;
1638}
1639
1640static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
1641{
1642 GSList msglist;
1643
1644 g_return_val_if_fail(msginfo != NULL, -1);
1645
1646 msglist.data = msginfo;
1647 msglist.next = NULL;
1648
1649 return imap_copy_msgs(folder, dest, &msglist);
1650}
1651
1652static gint imap_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
1653{
1654 MsgInfo *msginfo;
1655 GSList *file_list;
1656 gint ret;
1657
1658 g_return_val_if_fail(folder != NULL, -1);
1659 g_return_val_if_fail(dest != NULL, -1);
1660 g_return_val_if_fail(msglist != NULL, -1);
1661
1662 msginfo = (MsgInfo *)msglist->data;
1663 g_return_val_if_fail(msginfo->folder != NULL, -1);
1664
1665 if (folder == msginfo->folder->folder)
1666 return imap_do_copy_msgs(folder, dest, msglist, FALSE);
1667
1668 file_list = procmsg_get_message_file_list(msglist);
1669 g_return_val_if_fail(file_list != NULL, -1);
1670
1671 ret = imap_add_msgs(folder, dest, file_list, FALSE, NULL);
1672
1673 procmsg_message_file_list_free(file_list);
1674
1675 return ret;
1676}
1677
1678static gint imap_remove_msgs_by_seq_set(Folder *folder, FolderItem *item,
1679 GSList *seq_list)
1680{
1681 gint ok;
1682 IMAPSession *session;
1683 GSList *cur;
1684
1685 g_return_val_if_fail(seq_list != NULL, -1);
1686
1687 session = imap_session_get(folder);
1688 if (!session) return -1;
1689
1690 for (cur = seq_list; cur != NULL; cur = cur->next) {
1691 gchar *seq_set = (gchar *)cur->data;
1692
1693 status_print(_("Removing messages %s"), seq_set);
1694 ui_update();
1695
1696 ok = imap_set_message_flags(session, seq_set, IMAP_FLAG_DELETED,
1697 TRUE);
1698 if (ok != IMAP_SUCCESS) {
1699 log_warning(_("can't set deleted flags: %s\n"),
1700 seq_set);
1701 return ok;
1702 }
1703 }
1704
1705 ok = imap_cmd_expunge(session);
1706 if (ok != IMAP_SUCCESS) {
1707 log_warning(_("can't expunge\n"));
1708 } else {
1709 /* for some broken IMAP servers */
1710 ok = imap_cmd_noop(session);
1711 }
1712
1713 item->updated = TRUE;
1714
1715 return ok;
1716}
1717
1718static gint imap_remove_msg(Folder *folder, FolderItem *item, MsgInfo *msginfo)
1719{
1720 GSList msglist;
1721
1722 g_return_val_if_fail(msginfo != NULL, -1);
1723
1724 msglist.data = msginfo;
1725 msglist.next = NULL;
1726
1727 return imap_remove_msgs(folder, item, &msglist);
1728}
1729
1730static gint imap_remove_msgs(Folder *folder, FolderItem *item, GSList *msglist)
1731{
1732 gint ok;
1733 IMAPSession *session;
1734 GSList *seq_list, *cur;
1735 gchar *dir;
1736 gboolean dir_exist;
1737
1738 g_return_val_if_fail(folder != NULL, -1);
1739 g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1);
1740 g_return_val_if_fail(item != NULL, -1);
1741 g_return_val_if_fail(msglist != NULL, -1);
1742
1743 session = imap_session_get(folder);
1744 if (!session) return -1;
1745
1746 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1747 NULL, NULL, NULL, NULL);
1748 if (ok != IMAP_SUCCESS)
1749 return ok;
1750
1751 seq_list = imap_get_seq_set_from_msglist(msglist, 0);
1752 ok = imap_remove_msgs_by_seq_set(folder, item, seq_list);
1753 imap_seq_set_free(seq_list);
1754 if (ok != IMAP_SUCCESS)
1755 return ok;
1756
1757 dir = folder_item_get_path(item);
1758 dir_exist = is_dir_exist(dir);
1759 for (cur = msglist; cur != NULL; cur = cur->next) {
1760 MsgInfo *msginfo = (MsgInfo *)cur->data;
1761 guint32 uid = msginfo->msgnum;
1762
1763 if (syl_app_get())
1764 g_signal_emit_by_name(syl_app_get(), "remove-msg", item, NULL, uid);
1765
1766 if (dir_exist)
1767 remove_numbered_files(dir, uid, uid);
1768 item->total--;
1769 if (MSG_IS_NEW(msginfo->flags))
1770 item->new--;
1771 if (MSG_IS_UNREAD(msginfo->flags))
1772 item->unread--;
1773 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID);
1774 }
1775 g_free(dir);
1776
1777 return IMAP_SUCCESS;
1778}
1779
1780static gint imap_remove_all_msg(Folder *folder, FolderItem *item)
1781{
1782 gint ok;
1783 IMAPSession *session;
1784 gchar *dir;
1785
1786 g_return_val_if_fail(folder != NULL, -1);
1787 g_return_val_if_fail(item != NULL, -1);
1788
1789 session = imap_session_get(folder);
1790 if (!session) return -1;
1791
1792 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1793 NULL, NULL, NULL, NULL);
1794 if (ok != IMAP_SUCCESS)
1795 return ok;
1796
1797 status_print(_("Removing all messages in %s"), item->path);
1798 ui_update();
1799
1800 ok = imap_cmd_gen_send(session, "STORE 1:* +FLAGS.SILENT (\\Deleted)");
1801 if (ok != IMAP_SUCCESS) {
1802 log_warning(_("can't set deleted flags: 1:*\n"));
1803 return ok;
1804 }
1805 ok = imap_cmd_ok(session, NULL);
1806 if (ok != IMAP_SUCCESS) {
1807 log_warning(_("can't set deleted flags: 1:*\n"));
1808 return ok;
1809 }
1810
1811 ok = imap_cmd_expunge(session);
1812 if (ok != IMAP_SUCCESS) {
1813 log_warning(_("can't expunge\n"));
1814 return ok;
1815 }
1816
1817 if (syl_app_get())
1818 g_signal_emit_by_name(syl_app_get(), "remove-all-msg", item);
1819
1820 item->new = item->unread = item->total = 0;
1821 item->updated = TRUE;
1822
1823 dir = folder_item_get_path(item);
1824 if (is_dir_exist(dir))
1825 remove_all_numbered_files(dir);
1826 g_free(dir);
1827
1828 return IMAP_SUCCESS;
1829}
1830
1831static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item,
1832 MsgInfo *msginfo)
1833{
1834 /* TODO: properly implement this method */
1835 return FALSE;
1836}
1837
1838static gint imap_close(Folder *folder, FolderItem *item)
1839{
1840 gint ok;
1841 IMAPSession *session;
1842
1843 g_return_val_if_fail(folder != NULL, -1);
1844
1845 if (!item->path) return 0;
1846
1847 if (!REMOTE_FOLDER(folder)->session)
1848 return 0;
1849
1850 session = imap_session_get(folder);
1851 if (!session) return -1;
1852
1853 if (session->mbox) {
1854 if (strcmp2(session->mbox, item->path) != 0) return -1;
1855
1856 ok = imap_cmd_close(session);
1857 if (ok != IMAP_SUCCESS)
1858 log_warning(_("can't close folder\n"));
1859
1860 g_free(session->mbox);
1861 session->mbox = NULL;
1862
1863 return ok;
1864 } else
1865 return 0;
1866}
1867
1868static gint imap_scan_folder(Folder *folder, FolderItem *item)
1869{
1870 IMAPSession *session;
1871 gint messages, recent, unseen;
1872 guint32 uid_next, uid_validity;
1873 gint ok;
1874
1875 g_return_val_if_fail(folder != NULL, -1);
1876 g_return_val_if_fail(item != NULL, -1);
1877
1878 session = imap_session_get(folder);
1879 if (!session) return -1;
1880
1881 ok = imap_status(session, IMAP_FOLDER(folder), item->path,
1882 &messages, &recent, &uid_next, &uid_validity, &unseen);
1883 if (ok != IMAP_SUCCESS) return -1;
1884
1885 item->new = unseen > 0 ? recent : 0;
1886 item->unread = unseen;
1887 item->total = messages;
1888 item->last_num = (messages > 0 && uid_next > 0) ? uid_next - 1 : 0;
1889 /* item->mtime = uid_validity; */
1890 item->updated = TRUE;
1891
1892 return 0;
1893}
1894
1895static gint imap_scan_tree(Folder *folder)
1896{
1897 FolderItem *item = NULL;
1898 IMAPSession *session;
1899 gchar *root_folder = NULL;
1900 GSList *item_list, *cur;
1901
1902 g_return_val_if_fail(folder != NULL, -1);
1903 g_return_val_if_fail(folder->account != NULL, -1);
1904
1905 session = imap_session_get(folder);
1906 if (!session) {
1907 if (!folder->node) {
1908 folder_tree_destroy(folder);
1909 item = folder_item_new(folder->name, NULL);
1910 item->folder = folder;
1911 folder->node = item->node = g_node_new(item);
1912 }
1913 return -1;
1914 }
1915
1916 if (folder->account->imap_dir && *folder->account->imap_dir) {
1917 gchar *real_path;
1918 GPtrArray *argbuf;
1919 gint ok;
1920
1921 Xstrdup_a(root_folder, folder->account->imap_dir, return -1);
1922 extract_quote(root_folder, '"');
1923 subst_char(root_folder,
1924 imap_get_path_separator(IMAP_FOLDER(folder),
1925 root_folder),
1926 '/');
1927 strtailchomp(root_folder, '/');
1928 real_path = imap_get_real_path
1929 (IMAP_FOLDER(folder), root_folder);
1930 debug_print("IMAP root directory: %s\n", real_path);
1931
1932 /* check if root directory exist */
1933 argbuf = g_ptr_array_new();
1934 ok = imap_cmd_list(session, NULL, real_path, argbuf);
1935 if (ok != IMAP_SUCCESS ||
1936 search_array_str(argbuf, "LIST ") == NULL) {
1937 log_warning(_("root folder %s not exist\n"), real_path);
1938 g_ptr_array_free(argbuf, TRUE);
1939 g_free(real_path);
1940 return -1;
1941 }
1942 g_ptr_array_free(argbuf, TRUE);
1943 g_free(real_path);
1944 }
1945
1946 if (folder->node)
1947 item = FOLDER_ITEM(folder->node->data);
1948 if (!item || ((item->path || root_folder) &&
1949 strcmp2(item->path, root_folder) != 0)) {
1950 folder_tree_destroy(folder);
1951 item = folder_item_new(folder->name, root_folder);
1952 item->folder = folder;
1953 folder->node = item->node = g_node_new(item);
1954 }
1955
1956 item_list = imap_get_folder_list(session, item);
1957 imap_scan_tree_recursive(session, item, item_list);
1958 imap_create_missing_folders(folder);
1959
1960 for (cur = item_list; cur != NULL; cur = cur->next)
1961 folder_item_destroy(FOLDER_ITEM(cur->data));
1962 g_slist_free(item_list);
1963
1964 return 0;
1965}
1966
1967static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item,
1968 GSList *item_list)
1969{
1970 Folder *folder;
1971 FolderItem *new_item;
1972 GSList *part_list, *cur;
1973 GNode *node;
1974
1975 g_return_val_if_fail(item != NULL, -1);
1976 g_return_val_if_fail(item->folder != NULL, -1);
1977 g_return_val_if_fail(item->no_sub == FALSE, -1);
1978
1979 folder = item->folder;
1980
1981 part_list = imap_get_part_folder_list(item_list, item);
1982
1983 node = item->node->children;
1984 while (node != NULL) {
1985 FolderItem *old_item = FOLDER_ITEM(node->data);
1986 GNode *next = node->next;
1987
1988 new_item = NULL;
1989
1990 for (cur = part_list; cur != NULL; cur = cur->next) {
1991 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1992 if (!strcmp2(old_item->path, cur_item->path)) {
1993 new_item = cur_item;
1994 break;
1995 }
1996 }
1997 if (!new_item) {
1998 if (old_item->stype != F_VIRTUAL) {
1999 debug_print("folder '%s' not found. removing...\n", old_item->path);
2000 folder_item_remove(old_item);
2001 }
2002 } else if (old_item->stype == F_VIRTUAL) {
2003 debug_print("IMAP4 folder found at the location of virtual folder '%s'. removing virtual folder...\n", old_item->path);
2004 virtual_get_class()->remove_folder
2005 (folder, old_item);
2006 } else {
2007 old_item->no_sub = new_item->no_sub;
2008 old_item->no_select = new_item->no_select;
2009 if (old_item->no_select == TRUE)
2010 old_item->new = old_item->unread =
2011 old_item->total = 0;
2012 if (old_item->no_sub == TRUE && node->children) {
2013 debug_print("folder '%s' doesn't have "
2014 "subfolders. removing...\n",
2015 old_item->path);
2016 folder_item_remove_children(old_item);
2017 }
2018 }
2019
2020 node = next;
2021 }
2022
2023 for (cur = part_list; cur != NULL; cur = cur->next) {
2024 FolderItem *cur_item = FOLDER_ITEM(cur->data);
2025 new_item = NULL;
2026 for (node = item->node->children; node != NULL;
2027 node = node->next) {
2028 if (!strcmp2(FOLDER_ITEM(node->data)->path,
2029 cur_item->path)) {
2030 new_item = FOLDER_ITEM(node->data);
2031 cur_item = NULL;
2032 break;
2033 }
2034 }
2035 if (!new_item) {
2036 new_item = folder_item_copy(cur_item);
2037 debug_print("new folder '%s' found.\n", new_item->path);
2038 folder_item_append(item, new_item);
2039 }
2040
2041 if (!g_ascii_strcasecmp(new_item->path, "INBOX")) {
2042 new_item->stype = F_INBOX;
2043 folder->inbox = new_item;
2044 } else if (!item->parent || item->stype == F_INBOX) {
2045 const gchar *base;
2046
2047 base = g_basename(new_item->path);
2048
2049 if (!folder->outbox &&
2050 !g_ascii_strcasecmp(base, "Sent")) {
2051 new_item->stype = F_OUTBOX;
2052 folder->outbox = new_item;
2053 } else if (!folder->draft &&
2054 !g_ascii_strcasecmp(base, "Drafts")) {
2055 new_item->stype = F_DRAFT;
2056 folder->draft = new_item;
2057 } else if (!folder->queue &&
2058 !g_ascii_strcasecmp(base, "Queue")) {
2059 new_item->stype = F_QUEUE;
2060 folder->queue = new_item;
2061 } else if (!folder->trash &&
2062 !g_ascii_strcasecmp(base, "Trash")) {
2063 new_item->stype = F_TRASH;
2064 folder->trash = new_item;
2065 }
2066 }
2067
2068#if 0
2069 if (new_item->no_select == FALSE)
2070 imap_scan_folder(folder, new_item);
2071#endif
2072 if (new_item->no_sub == FALSE)
2073 imap_scan_tree_recursive(session, new_item, item_list);
2074 }
2075
2076 g_slist_free(part_list);
2077
2078 return IMAP_SUCCESS;
2079}
2080
2081static GSList *imap_get_folder_list(IMAPSession *session, FolderItem *item)
2082{
2083 Folder *folder;
2084 IMAPFolder *imapfolder;
2085 gchar *real_path;
2086 gchar *wildcard_path;
2087 gchar separator;
2088 GSList *item_list = NULL;
2089
2090 folder = item->folder;
2091 imapfolder = IMAP_FOLDER(folder);
2092
2093 separator = imap_get_path_separator(imapfolder, item->path);
2094
2095 if (folder->ui_func)
2096 folder->ui_func(folder, item, folder->ui_func_data);
2097
2098 if (item->path) {
2099 real_path = imap_get_real_path(imapfolder, item->path);
2100 strtailchomp(real_path, separator);
2101 wildcard_path = g_strdup_printf("%s%c*", real_path, separator);
2102 } else {
2103 real_path = g_strdup("");
2104 wildcard_path = g_strdup("*");
2105 }
2106
2107 if (imap_cmd_gen_send(session, "LIST \"\" \"%s\"", wildcard_path) == IMAP_SUCCESS) {
2108 item_list = imap_parse_list(session, real_path, NULL);
2109 item_list = imap_add_inter_folders(item_list, item->path);
2110 }
2111 g_free(real_path);
2112 g_free(wildcard_path);
2113
2114 return item_list;
2115}
2116
2117static GSList *imap_parse_list(IMAPSession *session, const gchar *real_path,
2118 gchar *separator)
2119{
2120 gchar buf[IMAPBUFSIZE];
2121 gchar flags[256];
2122 gchar separator_str[16];
2123 gchar *p;
2124 const gchar *name;
2125 gchar *loc_name, *loc_path;
2126 GSList *item_list = NULL;
2127 GString *str;
2128 FolderItem *new_item;
2129
2130 debug_print("getting list of %s ...\n",
2131 *real_path ? real_path : "\"\"");
2132
2133 str = g_string_new(NULL);
2134
2135 for (;;) {
2136 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) {
2137 log_warning(_("error occurred while getting LIST.\n"));
2138 break;
2139 }
2140 strretchomp(buf);
2141 if (buf[0] != '*' || buf[1] != ' ') {
2142 log_print("IMAP4< %s\n", buf);
2143 if (sscanf(buf, "%*d %16s", buf) < 1 ||
2144 strcmp(buf, "OK") != 0)
2145 log_warning(_("error occurred while getting LIST.\n"));
2146
2147 break;
2148 }
2149 debug_print("IMAP4< %s\n", buf);
2150
2151 g_string_assign(str, buf);
2152 p = str->str + 2;
2153 if (strncmp(p, "LIST ", 5) != 0) continue;
2154 p += 5;
2155
2156 if (*p != '(') continue;
2157 p++;
2158 p = strchr_cpy(p, ')', flags, sizeof(flags));
2159 if (!p) continue;
2160 while (*p == ' ') p++;
2161
2162 p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str));
2163 if (!p) continue;
2164 extract_quote(separator_str, '"');
2165 if (!strcmp(separator_str, "NIL"))
2166 separator_str[0] = '\0';
2167 if (separator)
2168 *separator = separator_str[0];
2169
2170 buf[0] = '\0';
2171 while (*p == ' ') p++;
2172 if ((*p == '~' && *(p + 1) == '{') || *p == '{' || *p == '"')
2173 p = imap_parse_atom(session, p, buf, sizeof(buf), str);
2174 else
2175 strncpy2(buf, p, sizeof(buf));
2176 strtailchomp(buf, separator_str[0]);
2177 if (buf[0] == '\0') continue;
2178 if (!strcmp(buf, real_path)) continue;
2179
2180 if (separator_str[0] != '\0')
2181 subst_char(buf, separator_str[0], '/');
2182 name = g_basename(buf);
2183 if (name[0] == '.') continue;
2184
2185 loc_name = imap_modified_utf7_to_utf8(name);
2186 loc_path = imap_modified_utf7_to_utf8(buf);
2187 new_item = folder_item_new(loc_name, loc_path);
2188 if (strcasestr(flags, "\\Noinferiors") != NULL)
2189 new_item->no_sub = TRUE;
2190 if (g_ascii_strcasecmp(buf, "INBOX") != 0 &&
2191 strcasestr(flags, "\\Noselect") != NULL)
2192 new_item->no_select = TRUE;
2193
2194 item_list = g_slist_prepend(item_list, new_item);
2195
2196 debug_print("folder '%s' found.\n", loc_path);
2197 g_free(loc_path);
2198 g_free(loc_name);
2199 }
2200
2201 g_string_free(str, TRUE);
2202
2203 item_list = g_slist_reverse(item_list);
2204 return item_list;
2205}
2206
2207static GSList *imap_add_inter_folders(GSList *item_list, const gchar *root_path)
2208{
2209 FolderItem *item;
2210 GSList *cur;
2211 GSList *add_list = NULL;
2212 GHashTable *exist;
2213 const gchar *p;
2214 gint root_path_len = 0;
2215
2216 if (root_path)
2217 root_path_len = strlen(root_path);
2218
2219 exist = g_hash_table_new(g_str_hash, g_str_equal);
2220
2221 for (cur = item_list; cur != NULL; cur = cur->next) {
2222 item = FOLDER_ITEM(cur->data);
2223
2224 if (root_path_len > 0 &&
2225 strncmp(root_path, item->path, root_path_len) != 0)
2226 continue;
2227 p = item->path + root_path_len;
2228 if (root_path_len > 0 && *p != '/') continue;
2229 while (*p == '/') p++;
2230 if (*p == '\0') continue;
2231 g_hash_table_insert(exist, (gpointer)p, GINT_TO_POINTER(1));
2232 }
2233
2234 for (cur = item_list; cur != NULL; cur = cur->next) {
2235 const gchar *q, *r;
2236 gchar *parent, *full_parent;
2237 FolderItem *new_item;
2238
2239 item = FOLDER_ITEM(cur->data);
2240
2241 if (root_path_len > 0 &&
2242 strncmp(root_path, item->path, root_path_len) != 0)
2243 continue;
2244 p = item->path + root_path_len;
2245 if (root_path_len > 0 && *p != '/') continue;
2246 while (*p == '/') p++;
2247 if (*p == '\0') continue;
2248
2249 q = p;
2250 while ((q = strchr(q, '/')) != NULL) {
2251 parent = g_strndup(p, q - p);
2252 if (!g_hash_table_lookup(exist, parent)) {
2253 if (root_path_len > 0)
2254 full_parent = g_strconcat
2255 (root_path, "/", parent, NULL);
2256 else
2257 full_parent = g_strdup(parent);
2258 new_item = folder_item_new(g_basename(parent),
2259 full_parent);
2260 new_item->no_select = TRUE;
2261 add_list = g_slist_prepend(add_list, new_item);
2262 r = new_item->path + root_path_len;
2263 while (*r == '/') r++;
2264 g_hash_table_insert(exist, (gpointer)r,
2265 GINT_TO_POINTER(1));
2266 debug_print("intermediate folder '%s' added\n",
2267 full_parent);
2268 g_free(full_parent);
2269 }
2270 g_free(parent);
2271 while (*q == '/') q++;
2272 }
2273 }
2274
2275 g_hash_table_destroy(exist);
2276
2277 add_list = g_slist_reverse(add_list);
2278 item_list = g_slist_concat(item_list, add_list);
2279
2280 return item_list;
2281}
2282
2283static GSList *imap_get_part_folder_list(GSList *item_list, FolderItem *item)
2284{
2285 FolderItem *cur_item;
2286 GSList *part_list = NULL, *cur;
2287 gint len;
2288
2289 if (!item->path) {
2290 debug_print("imap_get_part_folder_list(): get root folders\n");
2291 for (cur = item_list; cur != NULL; cur = cur->next) {
2292 cur_item = FOLDER_ITEM(cur->data);
2293
2294 if (!strchr(cur_item->path, '/')) {
2295 part_list = g_slist_prepend(part_list,
2296 cur_item);
2297 debug_print("append '%s'\n", cur_item->path);
2298 }
2299 }
2300 part_list = g_slist_reverse(part_list);
2301 return part_list;
2302 }
2303
2304 len = strlen(item->path);
2305 debug_print("imap_get_part_folder_list(): get folders under '%s'\n",
2306 item->path);
2307
2308 for (cur = item_list; cur != NULL; cur = cur->next) {
2309 cur_item = FOLDER_ITEM(cur->data);
2310
2311 if (!strncmp(cur_item->path, item->path, len) &&
2312 cur_item->path[len] == '/' &&
2313 !strchr(cur_item->path + len + 1, '/')) {
2314 part_list = g_slist_prepend(part_list, cur_item);
2315 debug_print("append '%s'\n", cur_item->path);
2316 }
2317 }
2318
2319 part_list = g_slist_reverse(part_list);
2320 return part_list;
2321}
2322
2323static gint imap_create_tree(Folder *folder)
2324{
2325 g_return_val_if_fail(folder != NULL, -1);
2326 g_return_val_if_fail(folder->node != NULL, -1);
2327 g_return_val_if_fail(folder->node->data != NULL, -1);
2328 g_return_val_if_fail(folder->account != NULL, -1);
2329
2330 if (imap_scan_tree(folder) < 0)
2331 return -1;
2332 imap_create_missing_folders(folder);
2333
2334 return 0;
2335}
2336
2337static void imap_create_missing_folders(Folder *folder)
2338{
2339 g_return_if_fail(folder != NULL);
2340
2341 if (!folder->inbox)
2342 folder->inbox = imap_create_special_folder
2343 (folder, F_INBOX, "INBOX");
2344#if 0
2345 if (!folder->outbox)
2346 folder->outbox = imap_create_special_folder
2347 (folder, F_OUTBOX, "Sent");
2348 if (!folder->draft)
2349 folder->draft = imap_create_special_folder
2350 (folder, F_DRAFT, "Drafts");
2351 if (!folder->queue)
2352 folder->queue = imap_create_special_folder
2353 (folder, F_QUEUE, "Queue");
2354#endif
2355 if (!folder->trash)
2356 folder->trash = imap_create_special_folder
2357 (folder, F_TRASH, "Trash");
2358}
2359
2360static FolderItem *imap_create_special_folder(Folder *folder,
2361 SpecialFolderItemType stype,
2362 const gchar *name)
2363{
2364 FolderItem *item;
2365 FolderItem *new_item;
2366
2367 g_return_val_if_fail(folder != NULL, NULL);
2368 g_return_val_if_fail(folder->node != NULL, NULL);
2369 g_return_val_if_fail(folder->node->data != NULL, NULL);
2370 g_return_val_if_fail(folder->account != NULL, NULL);
2371 g_return_val_if_fail(name != NULL, NULL);
2372
2373 item = FOLDER_ITEM(folder->node->data);
2374 new_item = imap_create_folder(folder, item, name);
2375
2376 if (!new_item) {
2377 g_warning(_("Can't create '%s'\n"), name);
2378 if (!folder->inbox) return NULL;
2379
2380 new_item = imap_create_folder(folder, folder->inbox, name);
2381 if (!new_item)
2382 g_warning(_("Can't create '%s' under INBOX\n"), name);
2383 else
2384 new_item->stype = stype;
2385 } else
2386 new_item->stype = stype;
2387
2388 return new_item;
2389}
2390
2391static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
2392 const gchar *name)
2393{
2394 gchar *dirpath, *imap_path;
2395 IMAPSession *session;
2396 FolderItem *new_item;
2397 gchar separator;
2398 gchar *new_name;
2399 const gchar *p;
2400 gboolean no_sub = FALSE, no_select = FALSE;
2401 gint ok;
2402
2403 g_return_val_if_fail(folder != NULL, NULL);
2404 g_return_val_if_fail(folder->account != NULL, NULL);
2405 g_return_val_if_fail(parent != NULL, NULL);
2406 g_return_val_if_fail(name != NULL, NULL);
2407
2408 session = imap_session_get(folder);
2409 if (!session) return NULL;
2410
2411 if (!parent->parent && g_ascii_strcasecmp(name, "INBOX") == 0)
2412 dirpath = g_strdup(name);
2413 else if (parent->path)
2414 dirpath = g_strconcat(parent->path, "/", name, NULL);
2415 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
2416 dirpath = g_strdup(name);
2417 else if (folder->account->imap_dir && *folder->account->imap_dir) {
2418 gchar *imap_dir;
2419
2420 Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL);
2421 strtailchomp(imap_dir, '/');
2422 dirpath = g_strconcat(imap_dir, "/", name, NULL);
2423 } else
2424 dirpath = g_strdup(name);
2425
2426 /* keep trailing directory separator to create a folder that contains
2427 sub folder */
2428 imap_path = imap_utf8_to_modified_utf7(dirpath);
2429 strtailchomp(dirpath, '/');
2430 Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;});
2431 strtailchomp(new_name, '/');
2432 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
2433 imap_path_separator_subst(imap_path, separator);
2434 subst_char(new_name, '/', separator);
2435
2436 if (g_ascii_strcasecmp(name, "INBOX") != 0) {
2437 GPtrArray *argbuf;
2438 gint i;
2439 gboolean exist = FALSE;
2440
2441 argbuf = g_ptr_array_new();
2442 ok = imap_cmd_list(session, NULL, imap_path, argbuf);
2443 if (ok != IMAP_SUCCESS) {
2444 log_warning(_("can't create mailbox: LIST failed\n"));
2445 g_free(imap_path);
2446 g_free(dirpath);
2447 g_ptr_array_free(argbuf, TRUE);
2448 return NULL;
2449 }
2450
2451 for (i = 0; i < argbuf->len; i++) {
2452 gchar *str;
2453 str = g_ptr_array_index(argbuf, i);
2454 if (!strncmp(str, "LIST ", 5)) {
2455 exist = TRUE;
2456 if (strcasestr(str + 5, "\\Noinferiors"))
2457 no_sub = TRUE;
2458 if (strcasestr(str + 5, "\\Noselect"))
2459 no_select = TRUE;
2460 break;
2461 }
2462 }
2463
2464 g_ptr_array_free(argbuf, TRUE);
2465 argbuf = NULL;
2466
2467 if (!exist) {
2468 ok = imap_cmd_create(session, imap_path);
2469 if (ok != IMAP_SUCCESS) {
2470 log_warning(_("can't create mailbox\n"));
2471 g_free(imap_path);
2472 g_free(dirpath);
2473 return NULL;
2474 }
2475
2476 ok = imap_cmd_subscribe(session, imap_path);
2477 if (ok != IMAP_SUCCESS) {
2478 log_warning(_("can't subscribe mailbox\n"));
2479 }
2480
2481 argbuf = g_ptr_array_new();
2482 ok = imap_cmd_list(session, NULL, imap_path, argbuf);
2483 if (ok != IMAP_SUCCESS) {
2484 log_warning("LIST failed\n");
2485 g_free(imap_path);
2486 g_free(dirpath);
2487 g_ptr_array_free(argbuf, TRUE);
2488 return NULL;
2489 }
2490
2491 for (i = 0; i < argbuf->len; i++) {
2492 gchar *str;
2493 str = g_ptr_array_index(argbuf, i);
2494 if (!strncmp(str, "LIST ", 5)) {
2495 if (strcasestr(str + 5, "\\Noinferiors"))
2496 no_sub = TRUE;
2497 if (strcasestr(str + 5, "\\Noselect"))
2498 no_select = TRUE;
2499 break;
2500 }
2501 }
2502 g_ptr_array_free(argbuf, TRUE);
2503 }
2504
2505 }
2506
2507 new_item = folder_item_new(new_name, dirpath);
2508 new_item->no_sub = no_sub;
2509 new_item->no_select = no_select;
2510 folder_item_append(parent, new_item);
2511 g_free(imap_path);
2512 g_free(dirpath);
2513
2514 dirpath = folder_item_get_path(new_item);
2515 if (!is_dir_exist(dirpath))
2516 make_dir_hier(dirpath);
2517 g_free(dirpath);
2518
2519 return new_item;
2520}
2521
2522static gint imap_rename_folder_real(Folder *folder, FolderItem *item,
2523 FolderItem *new_parent, const gchar *name)
2524{
2525 gchar *newpath;
2526 gchar *real_oldpath;
2527 gchar *real_newpath;
2528 gchar *paths[2];
2529 gchar *old_cache_dir;
2530 gchar *new_cache_dir;
2531 IMAPSession *session;
2532 gchar separator;
2533 gint ok;
2534 gint exists, recent, unseen;
2535 guint32 uid_validity;
2536 gchar *old_id, *new_id;
2537
2538 g_return_val_if_fail(folder != NULL, -1);
2539 g_return_val_if_fail(item != NULL, -1);
2540 g_return_val_if_fail(folder == item->folder, -1);
2541 g_return_val_if_fail(item->path != NULL, -1);
2542 g_return_val_if_fail(new_parent != NULL || name != NULL, -1);
2543 if (new_parent) {
2544 g_return_val_if_fail(item != new_parent, -1);
2545 g_return_val_if_fail(item->parent != new_parent, -1);
2546 g_return_val_if_fail(item->folder == new_parent->folder, -1);
2547 if (g_node_is_ancestor(item->node, new_parent->node)) {
2548 g_warning("folder to be moved is ancestor of new parent\n");
2549 return -1;
2550 }
2551 }
2552
2553 session = imap_session_get(folder);
2554 if (!session) return -1;
2555
2556 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
2557
2558 g_free(session->mbox);
2559 session->mbox = NULL;
2560 ok = imap_cmd_examine(session, "INBOX",
2561 &exists, &recent, &unseen, &uid_validity);
2562 if (ok != IMAP_SUCCESS) {
2563 g_free(real_oldpath);
2564 return -1;
2565 }
2566
2567 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
2568 if (new_parent) {
2569 if (name) {
2570 if (new_parent->path)
2571 newpath = g_strconcat(new_parent->path,
2572 "/", name, NULL);
2573 else
2574 newpath = g_strdup(name);
2575 } else {
2576 gchar *name_;
2577
2578 name_ = g_path_get_basename(item->path);
2579 if (new_parent->path)
2580 newpath = g_strconcat(new_parent->path,
2581 "/", name_, NULL);
2582 else
2583 newpath = g_strdup(name_);
2584 AUTORELEASE_STR(name_, );
2585 name = name_;
2586 }
2587 } else {
2588 if (strchr(item->path, '/')) {
2589 gchar *dirpath;
2590
2591 dirpath = g_dirname(item->path);
2592 newpath = g_strconcat(dirpath, "/", name, NULL);
2593 g_free(dirpath);
2594 } else
2595 newpath = g_strdup(name);
2596 }
2597
2598 real_newpath = imap_utf8_to_modified_utf7(newpath);
2599 imap_path_separator_subst(real_newpath, separator);
2600
2601 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
2602 if (ok != IMAP_SUCCESS) {
2603 log_warning(_("can't rename mailbox: %s to %s\n"),
2604 real_oldpath, real_newpath);
2605 g_free(real_oldpath);
2606 g_free(newpath);
2607 g_free(real_newpath);
2608 return -1;
2609 }
2610
2611 old_id = folder_item_get_identifier(item);
2612
2613 if (new_parent) {
2614 g_node_unlink(item->node);
2615 g_node_append(new_parent->node, item->node);
2616 item->parent = new_parent;
2617 }
2618
2619 g_free(item->name);
2620 item->name = g_strdup(name);
2621
2622 old_cache_dir = folder_item_get_path(item);
2623
2624 paths[0] = g_strdup(item->path);
2625 paths[1] = newpath;
2626 g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2627 imap_rename_folder_func, paths);
2628
2629 if (is_dir_exist(old_cache_dir)) {
2630 new_cache_dir = folder_item_get_path(item);
2631 if (g_rename(old_cache_dir, new_cache_dir) < 0) {
2632 FILE_OP_ERROR(old_cache_dir, "rename");
2633 }
2634 g_free(new_cache_dir);
2635 }
2636
2637 g_free(old_cache_dir);
2638 g_free(paths[0]);
2639 g_free(newpath);
2640 g_free(real_oldpath);
2641 g_free(real_newpath);
2642
2643 new_id = folder_item_get_identifier(item);
2644 if (syl_app_get())
2645 g_signal_emit_by_name(syl_app_get(), "move-folder", item,
2646 old_id, new_id);
2647 g_free(new_id);
2648 g_free(old_id);
2649
2650 return 0;
2651}
2652
2653static gint imap_rename_folder(Folder *folder, FolderItem *item,
2654 const gchar *name)
2655{
2656 return imap_rename_folder_real(folder, item, NULL, name);
2657}
2658
2659static gint imap_move_folder(Folder *folder, FolderItem *item,
2660 FolderItem *new_parent)
2661{
2662 return imap_rename_folder_real(folder, item, new_parent, NULL);
2663}
2664
2665static gint imap_remove_folder(Folder *folder, FolderItem *item)
2666{
2667 gint ok;
2668 IMAPSession *session;
2669 gchar *path;
2670 gchar *cache_dir;
2671 gint exists, recent, unseen;
2672 guint32 uid_validity;
2673
2674 g_return_val_if_fail(folder != NULL, -1);
2675 g_return_val_if_fail(item != NULL, -1);
2676 g_return_val_if_fail(item->path != NULL, -1);
2677
2678 session = imap_session_get(folder);
2679 if (!session) return -1;
2680
2681 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
2682
2683 ok = imap_cmd_examine(session, "INBOX",
2684 &exists, &recent, &unseen, &uid_validity);
2685 if (ok != IMAP_SUCCESS) {
2686 g_free(path);
2687 return -1;
2688 }
2689
2690 ok = imap_cmd_delete(session, path);
2691 if (ok != IMAP_SUCCESS) {
2692 log_warning(_("can't delete mailbox\n"));
2693 g_free(path);
2694 return -1;
2695 }
2696
2697 g_free(path);
2698 cache_dir = folder_item_get_path(item);
2699 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
2700 g_warning("can't remove directory '%s'\n", cache_dir);
2701 g_free(cache_dir);
2702
2703 if (syl_app_get())
2704 g_signal_emit_by_name(syl_app_get(), "remove-folder", item);
2705 folder_item_remove(item);
2706
2707 return 0;
2708}
2709
2710typedef struct _IMAPGetData
2711{
2712 FolderItem *item;
2713 gint exists;
2714 gboolean update_count;
2715 GSList *newlist;
2716} IMAPGetData;
2717
2718static gint imap_get_uncached_messages_progress_func(IMAPSession *session,
2719 gint count, gint total,
2720 gpointer data)
2721{
2722 status_print(_("Getting message headers (%d / %d)"), count, total);
2723 progress_show(count, total);
2724#ifndef USE_THREADS
2725 ui_update();
2726#endif
2727 return 0;
2728}
2729
2730static gint imap_get_uncached_messages_func(IMAPSession *session, gpointer data)
2731{
2732 gchar *tmp;
2733 GSList *newlist = NULL;
2734 GSList *llast = NULL;
2735 GString *str;
2736 MsgInfo *msginfo;
2737 gint count = 1;
2738 GTimeVal tv_prev, tv_cur;
2739 IMAPGetData *get_data = (IMAPGetData *)data;
2740 FolderItem *item = get_data->item;
2741 gint exists = get_data->exists;
2742 gboolean update_count = get_data->update_count;
2743
2744 g_get_current_time(&tv_prev);
2745#ifndef USE_THREADS
2746 ui_update();
2747#endif
2748
2749#if USE_THREADS
2750 ((IMAPRealSession *)session)->prog_total = exists;
2751#endif
2752
2753 str = g_string_new(NULL);
2754
2755 for (;;) {
2756 if (exists > 0 && count <= exists) {
2757 g_get_current_time(&tv_cur);
2758 if (tv_cur.tv_sec > tv_prev.tv_sec ||
2759 tv_cur.tv_usec - tv_prev.tv_usec >
2760 PROGRESS_UPDATE_INTERVAL * 1000) {
2761#if USE_THREADS
2762 ((IMAPRealSession *)session)->prog_count = count;
2763 g_main_context_wakeup(NULL);
2764#else
2765 imap_get_uncached_messages_progress_func
2766 (session, count, exists, data);
2767#endif
2768 tv_prev = tv_cur;
2769 }
2770 }
2771 ++count;
2772
2773 if (sock_getline(SESSION(session)->sock, &tmp) < 0) {
2774 log_warning(_("error occurred while getting envelope.\n"));
2775 g_string_free(str, TRUE);
2776 return IMAP_SOCKET;
2777 }
2778 strretchomp(tmp);
2779 if (tmp[0] != '*' || tmp[1] != ' ') {
2780 log_print("IMAP4< %s\n", tmp);
2781 g_free(tmp);
2782 break;
2783 }
2784 if (strstr(tmp, "FETCH") == NULL) {
2785 log_print("IMAP4< %s\n", tmp);
2786 g_free(tmp);
2787 continue;
2788 }
2789 log_print("IMAP4< %s\n", tmp);
2790 g_string_assign(str, tmp);
2791 g_free(tmp);
2792
2793 msginfo = imap_parse_envelope(session, item, str);
2794 if (!msginfo) {
2795 log_warning(_("can't parse envelope: %s\n"), str->str);
2796 continue;
2797 }
2798 if (update_count) {
2799 if (MSG_IS_NEW(msginfo->flags))
2800 item->new++;
2801 if (MSG_IS_UNREAD(msginfo->flags))
2802 item->unread++;
2803 }
2804 if (item->stype == F_QUEUE) {
2805 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
2806 } else if (item->stype == F_DRAFT) {
2807 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
2808 }
2809
2810 msginfo->folder = item;
2811
2812 if (!newlist)
2813 llast = newlist = g_slist_append(newlist, msginfo);
2814 else {
2815 llast = g_slist_append(llast, msginfo);
2816 llast = llast->next;
2817 }
2818
2819 if (update_count)
2820 item->total++;
2821 }
2822
2823 g_string_free(str, TRUE);
2824
2825 session_set_access_time(SESSION(session));
2826
2827 get_data->newlist = newlist;
2828 return IMAP_SUCCESS;
2829}
2830
2831static GSList *imap_get_uncached_messages(IMAPSession *session,
2832 FolderItem *item,
2833 guint32 first_uid, guint32 last_uid,
2834 gint exists, gboolean update_count)
2835{
2836 IMAPGetData get_data = {item, exists, update_count, NULL};
2837 gchar seq_set[22];
2838 gint ok;
2839
2840 g_return_val_if_fail(session != NULL, NULL);
2841 g_return_val_if_fail(item != NULL, NULL);
2842 g_return_val_if_fail(item->folder != NULL, NULL);
2843 g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_IMAP, NULL);
2844 g_return_val_if_fail(first_uid <= last_uid, NULL);
2845
2846 if (first_uid == 0 && last_uid == 0)
2847 strcpy(seq_set, "1:*");
2848 else
2849 g_snprintf(seq_set, sizeof(seq_set), "%u:%u",
2850 first_uid, last_uid);
2851 if (imap_cmd_envelope(session, seq_set) != IMAP_SUCCESS) {
2852 log_warning(_("can't get envelope\n"));
2853 return NULL;
2854 }
2855
2856#if USE_THREADS
2857 ok = imap_thread_run_progress(session, imap_get_uncached_messages_func,
2858 imap_get_uncached_messages_progress_func,
2859 &get_data);
2860#else
2861 ok = imap_get_uncached_messages_func(session, &get_data);
2862#endif
2863
2864 progress_show(0, 0);
2865 return get_data.newlist;
2866}
2867
2868static void imap_delete_cached_message(FolderItem *item, guint32 uid)
2869{
2870 gchar *dir;
2871 gchar *file;
2872
2873 g_return_if_fail(item != NULL);
2874 g_return_if_fail(item->folder != NULL);
2875 g_return_if_fail(FOLDER_TYPE(item->folder) == F_IMAP);
2876
2877 dir = folder_item_get_path(item);
2878 file = g_strdup_printf("%s%c%u", dir, G_DIR_SEPARATOR, uid);
2879
2880 debug_print("Deleting cached message: %s\n", file);
2881
2882 g_unlink(file);
2883
2884 g_free(file);
2885 g_free(dir);
2886}
2887
2888static GSList *imap_delete_cached_messages(GSList *mlist, FolderItem *item,
2889 guint32 first_uid, guint32 last_uid)
2890{
2891 GSList *cur, *next;
2892 MsgInfo *msginfo;
2893 gchar *dir;
2894
2895 g_return_val_if_fail(item != NULL, mlist);
2896 g_return_val_if_fail(item->folder != NULL, mlist);
2897 g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_IMAP, mlist);
2898
2899 if (first_uid == 0 && last_uid == 0)
2900 return mlist;
2901
2902 debug_print("Deleting cached messages %u - %u ... ",
2903 first_uid, last_uid);
2904
2905 dir = folder_item_get_path(item);
2906 if (is_dir_exist(dir))
2907 remove_numbered_files(dir, first_uid, last_uid);
2908 g_free(dir);
2909
2910 for (cur = mlist; cur != NULL; ) {
2911 next = cur->next;
2912
2913 msginfo = (MsgInfo *)cur->data;
2914 if (msginfo != NULL && first_uid <= msginfo->msgnum &&
2915 msginfo->msgnum <= last_uid) {
2916 procmsg_msginfo_free(msginfo);
2917 mlist = g_slist_remove(mlist, msginfo);
2918 }
2919
2920 cur = next;
2921 }
2922
2923 debug_print("done.\n");
2924
2925 return mlist;
2926}
2927
2928static void imap_delete_all_cached_messages(FolderItem *item)
2929{
2930 gchar *dir;
2931
2932 g_return_if_fail(item != NULL);
2933 g_return_if_fail(item->folder != NULL);
2934 g_return_if_fail(FOLDER_TYPE(item->folder) == F_IMAP);
2935
2936 debug_print("Deleting all cached messages... ");
2937
2938 dir = folder_item_get_path(item);
2939 if (is_dir_exist(dir))
2940 remove_all_numbered_files(dir);
2941 g_free(dir);
2942
2943 debug_print("done.\n");
2944}
2945
2946#if USE_SSL
2947static SockInfo *imap_open(const gchar *server, gushort port,
2948 SocksInfo *socks_info, SSLType ssl_type)
2949#else
2950static SockInfo *imap_open(const gchar *server, gushort port,
2951 SocksInfo *socks_info)
2952#endif
2953{
2954 SockInfo *sock = NULL;
2955 const gchar *server_;
2956 gushort port_;
2957#if USE_THREADS
2958 gint conn_id;
2959#endif
2960
2961 if (socks_info) {
2962 server_ = socks_info->proxy_host;
2963 port_ = socks_info->proxy_port;
2964 } else {
2965 server_ = server;
2966 port_ = port;
2967 }
2968
2969#if USE_THREADS
2970 if ((conn_id = sock_connect_async_thread(server_, port_)) < 0 ||
2971 sock_connect_async_thread_wait(conn_id, &sock) < 0) {
2972 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
2973 server, port);
2974 return NULL;
2975 }
2976#else
2977 if ((sock = sock_connect(server_, port_)) == NULL) {
2978 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
2979 server, port);
2980 return NULL;
2981 }
2982#endif
2983
2984 if (socks_info) {
2985 if (socks_connect(sock, server, port, socks_info) < 0) {
2986 log_warning("Can't establish SOCKS connection: %s:%d\n",
2987 server, port);
2988 sock_close(sock);
2989 return NULL;
2990 }
2991 }
2992
2993#if USE_SSL
2994 if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
2995 log_warning(_("Can't establish IMAP4 session with: %s:%d\n"),
2996 server, port);
2997 sock_close(sock);
2998 return NULL;
2999 }
3000#endif
3001
3002 return sock;
3003}
3004
3005static GList *imap_parse_namespace_str(gchar *str)
3006{
3007 gchar *p = str;
3008 gchar *name;
3009 gchar *separator;
3010 IMAPNameSpace *namespace;
3011 GList *ns_list = NULL;
3012
3013 while (*p != '\0') {
3014 /* parse ("#foo" "/") */
3015
3016 while (*p && *p != '(') p++;
3017 if (*p == '\0') break;
3018 p++;
3019
3020 while (*p && *p != '"') p++;
3021 if (*p == '\0') break;
3022 p++;
3023 name = p;
3024
3025 while (*p && *p != '"') p++;
3026 if (*p == '\0') break;
3027 *p = '\0';
3028 p++;
3029
3030 while (*p && g_ascii_isspace(*p)) p++;
3031 if (*p == '\0') break;
3032 if (strncmp(p, "NIL", 3) == 0)
3033 separator = NULL;
3034 else if (*p == '"') {
3035 p++;
3036 separator = p;
3037 while (*p && *p != '"') p++;
3038 if (*p == '\0') break;
3039 *p = '\0';
3040 p++;
3041 } else break;
3042
3043 while (*p && *p != ')') p++;
3044 if (*p == '\0') break;
3045 p++;
3046
3047 namespace = g_new(IMAPNameSpace, 1);
3048 namespace->name = g_strdup(name);
3049 namespace->separator = separator ? separator[0] : '\0';
3050 ns_list = g_list_append(ns_list, namespace);
3051 }
3052
3053 return ns_list;
3054}
3055
3056static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder)
3057{
3058 gchar *ns_str = NULL;
3059 gchar **str_array;
3060
3061 g_return_if_fail(session != NULL);
3062 g_return_if_fail(folder != NULL);
3063
3064 if (folder->ns_personal != NULL ||
3065 folder->ns_others != NULL ||
3066 folder->ns_shared != NULL)
3067 return;
3068
3069 if (imap_cmd_namespace(session, &ns_str) != IMAP_SUCCESS) {
3070 log_warning(_("can't get namespace\n"));
3071 imap_get_namespace_by_list(session, folder);
3072 return;
3073 }
3074
3075 str_array = strsplit_parenthesis(ns_str, '(', ')', 3);
3076 if (str_array[0])
3077 folder->ns_personal = imap_parse_namespace_str(str_array[0]);
3078 if (str_array[0] && str_array[1])
3079 folder->ns_others = imap_parse_namespace_str(str_array[1]);
3080 if (str_array[0] && str_array[1] && str_array[2])
3081 folder->ns_shared = imap_parse_namespace_str(str_array[2]);
3082 g_strfreev(str_array);
3083 g_free(ns_str);
3084}
3085
3086static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder)
3087{
3088 GSList *item_list, *cur;
3089 gchar separator = '\0';
3090 IMAPNameSpace *namespace;
3091
3092 g_return_if_fail(session != NULL);
3093 g_return_if_fail(folder != NULL);
3094
3095 if (folder->ns_personal != NULL ||
3096 folder->ns_others != NULL ||
3097 folder->ns_shared != NULL)
3098 return;
3099
3100 if (imap_cmd_gen_send(session, "LIST \"\" \"\"") != IMAP_SUCCESS)
3101 return;
3102 item_list = imap_parse_list(session, "", &separator);
3103 for (cur = item_list; cur != NULL; cur = cur->next)
3104 folder_item_destroy(FOLDER_ITEM(cur->data));
3105 g_slist_free(item_list);
3106
3107 namespace = g_new(IMAPNameSpace, 1);
3108 namespace->name = g_strdup("");
3109 namespace->separator = separator;
3110 folder->ns_personal = g_list_append(NULL, namespace);
3111}
3112
3113static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
3114 const gchar *path)
3115{
3116 IMAPNameSpace *namespace = NULL;
3117 gchar *tmp_path, *name;
3118
3119 if (!path) path = "";
3120
3121 for (; ns_list != NULL; ns_list = ns_list->next) {
3122 IMAPNameSpace *tmp_ns = ns_list->data;
3123
3124 Xstrcat_a(tmp_path, path, "/", return namespace);
3125 Xstrdup_a(name, tmp_ns->name, return namespace);
3126 if (tmp_ns->separator && tmp_ns->separator != '/') {
3127 subst_char(tmp_path, tmp_ns->separator, '/');
3128 subst_char(name, tmp_ns->separator, '/');
3129 }
3130 if (strncmp(tmp_path, name, strlen(name)) == 0)
3131 namespace = tmp_ns;
3132 }
3133
3134 return namespace;
3135}
3136
3137static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
3138 const gchar *path)
3139{
3140 IMAPNameSpace *namespace;
3141
3142 g_return_val_if_fail(folder != NULL, NULL);
3143
3144 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
3145 if (namespace) return namespace;
3146 namespace = imap_find_namespace_from_list(folder->ns_others, path);
3147 if (namespace) return namespace;
3148 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
3149 if (namespace) return namespace;
3150
3151 return NULL;
3152}
3153
3154static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
3155{
3156 IMAPNameSpace *namespace;
3157 gchar separator = '/';
3158
3159 namespace = imap_find_namespace(folder, path);
3160 if (namespace && namespace->separator)
3161 separator = namespace->separator;
3162
3163 return separator;
3164}
3165
3166static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
3167{
3168 gchar *real_path;
3169 gchar separator;
3170
3171 g_return_val_if_fail(folder != NULL, NULL);
3172 g_return_val_if_fail(path != NULL, NULL);
3173
3174 real_path = imap_utf8_to_modified_utf7(path);
3175 separator = imap_get_path_separator(folder, path);
3176 imap_path_separator_subst(real_path, separator);
3177
3178 return real_path;
3179}
3180
3181static gchar *imap_parse_atom(IMAPSession *session, gchar *src,
3182 gchar *dest, gint dest_len, GString *str)
3183{
3184 gchar *cur_pos = src;
3185 gchar *nextline;
3186
3187 g_return_val_if_fail(str != NULL, cur_pos);
3188
3189 /* read the next line if the current response buffer is empty */
3190 while (g_ascii_isspace(*cur_pos)) cur_pos++;
3191 while (*cur_pos == '\0') {
3192 if (sock_getline(SESSION(session)->sock, &nextline) < 0)
3193 return cur_pos;
3194 g_string_assign(str, nextline);
3195 cur_pos = str->str;
3196 strretchomp(nextline);
3197 /* log_print("IMAP4< %s\n", nextline); */
3198 debug_print("IMAP4< %s\n", nextline);
3199 g_free(nextline);
3200
3201 while (g_ascii_isspace(*cur_pos)) cur_pos++;
3202 }
3203
3204 if (*cur_pos == '~' && *(cur_pos + 1) == '{')
3205 cur_pos++;
3206
3207 if (!strncmp(cur_pos, "NIL", 3)) {
3208 *dest = '\0';
3209 cur_pos += 3;
3210 } else if (*cur_pos == '\"') {
3211 gchar *p;
3212
3213 p = get_quoted(cur_pos, '\"', dest, dest_len);
3214 cur_pos = p ? p : cur_pos + 2;
3215 } else if (*cur_pos == '{') {
3216 gchar buf[32];
3217 gint len;
3218 gint block_len = 0;
3219
3220 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
3221 len = atoi(buf);
3222 g_return_val_if_fail(len >= 0, cur_pos);
3223
3224 g_string_truncate(str, 0);
3225 cur_pos = str->str;
3226
3227 do {
3228 gint cur_len;
3229
3230 cur_len = sock_getline(SESSION(session)->sock,
3231 &nextline);
3232 if (cur_len < 0)
3233 return cur_pos;
3234 block_len += cur_len;
3235 subst_null(nextline, cur_len, ' ');
3236 g_string_append(str, nextline);
3237 cur_pos = str->str;
3238 strretchomp(nextline);
3239 /* log_print("IMAP4< %s\n", nextline); */
3240 debug_print("IMAP4< %s\n", nextline);
3241 g_free(nextline);
3242 } while (block_len < len);
3243
3244 memcpy(dest, cur_pos, MIN(len, dest_len - 1));
3245 dest[MIN(len, dest_len - 1)] = '\0';
3246 cur_pos += len;
3247 }
3248
3249 return cur_pos;
3250}
3251
3252static gchar *imap_get_header(IMAPSession *session, gchar *cur_pos,
3253 gchar **headers, GString *str)
3254{
3255 gchar *nextline;
3256 gchar buf[IMAPBUFSIZE];
3257 gint len;
3258 gint block_len = 0;
3259
3260 *headers = NULL;
3261
3262 g_return_val_if_fail(str != NULL, cur_pos);
3263
3264 while (g_ascii_isspace(*cur_pos)) cur_pos++;
3265 if (*cur_pos == '~' && *(cur_pos + 1) == '{')
3266 cur_pos++;
3267
3268 if (*cur_pos == '"') {
3269 cur_pos = strchr_cpy(cur_pos + 1, '"', buf, sizeof(buf));
3270 if (!cur_pos)
3271 return NULL;
3272 len = strlen(buf);
3273 *headers = g_strdup(buf);
3274 while (g_ascii_isspace(*cur_pos)) cur_pos++;
3275 return cur_pos;
3276 }
3277
3278 g_return_val_if_fail(*cur_pos == '{', cur_pos);
3279
3280 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
3281 len = atoi(buf);
3282 g_return_val_if_fail(len >= 0, cur_pos);
3283
3284 g_string_truncate(str, 0);
3285 cur_pos = str->str;
3286
3287 do {
3288 gint cur_len;
3289
3290 cur_len = sock_getline(SESSION(session)->sock, &nextline);
3291 if (cur_len < 0)
3292 return cur_pos;
3293 block_len += cur_len;
3294 subst_null(nextline, cur_len, ' ');
3295 g_string_append(str, nextline);
3296 cur_pos = str->str;
3297 /* strretchomp(nextline); */
3298 /* debug_print("IMAP4< %s\n", nextline); */
3299 g_free(nextline);
3300 } while (block_len < len);
3301
3302 debug_print("IMAP4< [contents of RFC822.HEADER]\n");
3303
3304 *headers = g_strndup(cur_pos, len);
3305 cur_pos += len;
3306
3307 while (g_ascii_isspace(*cur_pos)) cur_pos++;
3308 while (*cur_pos == '\0') {
3309 if (sock_getline(SESSION(session)->sock, &nextline) < 0)
3310 return cur_pos;
3311 g_string_assign(str, nextline);
3312 cur_pos = str->str;
3313 strretchomp(nextline);
3314 debug_print("IMAP4< %s\n", nextline);
3315 g_free(nextline);
3316
3317 while (g_ascii_isspace(*cur_pos)) cur_pos++;
3318 }
3319
3320 return cur_pos;
3321}
3322
3323static MsgFlags imap_parse_flags(const gchar *flag_str)
3324{
3325 const gchar *p = flag_str;
3326 MsgFlags flags = {0, 0};
3327
3328 flags.perm_flags = MSG_UNREAD;
3329
3330 while (*p != '\0') {
3331 if (g_ascii_strncasecmp(p, "\\Recent", 7) == 0 &&
3332 MSG_IS_UNREAD(flags)) {
3333 MSG_SET_PERM_FLAGS(flags, MSG_NEW);
3334 } else if (g_ascii_strncasecmp(p, "\\Seen", 5) == 0) {
3335 MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
3336 } else if (g_ascii_strncasecmp(p, "\\Deleted", 8) == 0) {
3337 MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
3338 } else if (g_ascii_strncasecmp(p, "\\Flagged", 8) == 0) {
3339 MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
3340 } else if (g_ascii_strncasecmp(p, "\\Answered", 9) == 0) {
3341 MSG_SET_PERM_FLAGS(flags, MSG_REPLIED);
3342 } else if (g_ascii_strncasecmp(p, "$label", 6) == 0) {
3343 /* color labels */
3344 if (*(p + 6) >= '1' && *(p + 6) <= '7') {
3345 guint color = *(p + 6) - '1' + 1;
3346 MSG_UNSET_PERM_FLAGS(flags,
3347 MSG_CLABEL_FLAG_MASK);
3348 MSG_SET_COLORLABEL_VALUE(flags, color);
3349 }
3350 }
3351
3352 while (*p && !g_ascii_isspace(*p)) p++;
3353 while (g_ascii_isspace(*p)) p++;
3354 }
3355
3356 return flags;
3357}
3358
3359static IMAPFlags imap_parse_imap_flags(const gchar *flag_str)
3360{
3361 const gchar *p = flag_str;
3362 IMAPFlags flags = 0;
3363
3364 while (*p != '\0') {
3365 if (g_ascii_strncasecmp(p, "\\Seen", 5) == 0) {
3366 flags |= IMAP_FLAG_SEEN;
3367 } else if (g_ascii_strncasecmp(p, "\\Deleted", 8) == 0) {
3368 flags |= IMAP_FLAG_DELETED;
3369 } else if (g_ascii_strncasecmp(p, "\\Flagged", 8) == 0) {
3370 flags |= IMAP_FLAG_FLAGGED;
3371 } else if (g_ascii_strncasecmp(p, "\\Answered", 9) == 0) {
3372 flags |= IMAP_FLAG_ANSWERED;
3373 } else if (g_ascii_strncasecmp(p, "$label", 6) == 0) {
3374 /* color labels */
3375 if (*(p + 6) >= '1' && *(p + 6) <= '7') {
3376 guint color = *(p + 6) - '1' + 1;
3377 MSG_UNSET_FLAGS(flags, MSG_CLABEL_FLAG_MASK);
3378 IMAP_SET_COLORLABEL_VALUE(flags, color);
3379 }
3380 }
3381
3382 while (*p && !g_ascii_isspace(*p)) p++;
3383 while (g_ascii_isspace(*p)) p++;
3384 }
3385
3386 return flags;
3387}
3388
3389static MsgInfo *imap_parse_envelope(IMAPSession *session, FolderItem *item,
3390 GString *line_str)
3391{
3392 gchar buf[IMAPBUFSIZE];
3393 MsgInfo *msginfo = NULL;
3394 gchar *cur_pos;
3395 gint msgnum;
3396 guint32 uid = 0;
3397 size_t size = 0;
3398 MsgFlags flags = {0, 0}, imap_flags = {0, 0};
3399
3400 g_return_val_if_fail(line_str != NULL, NULL);
3401 g_return_val_if_fail(line_str->str[0] == '*' &&
3402 line_str->str[1] == ' ', NULL);
3403
3404 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
3405 if (item->stype == F_QUEUE) {
3406 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3407 } else if (item->stype == F_DRAFT) {
3408 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3409 }
3410
3411 cur_pos = line_str->str + 2;
3412
3413#define PARSE_ONE_ELEMENT(ch) \
3414{ \
3415 cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
3416 if (cur_pos == NULL) { \
3417 g_warning("cur_pos == NULL\n"); \
3418 procmsg_msginfo_free(msginfo); \
3419 return NULL; \
3420 } \
3421}
3422
3423 PARSE_ONE_ELEMENT(' ');
3424 msgnum = atoi(buf);
3425
3426 PARSE_ONE_ELEMENT(' ');
3427 g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL);
3428
3429 g_return_val_if_fail(*cur_pos == '(', NULL);
3430 cur_pos++;
3431
3432 while (*cur_pos != '\0' && *cur_pos != ')') {
3433 while (*cur_pos == ' ') cur_pos++;
3434
3435 if (!strncmp(cur_pos, "UID ", 4)) {
3436 cur_pos += 4;
3437 uid = strtoul(cur_pos, &cur_pos, 10);
3438 } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
3439 cur_pos += 6;
3440 if (*cur_pos != '(') {
3441 g_warning("FLAGS: *cur_pos != '('\n");
3442 procmsg_msginfo_free(msginfo);
3443 return NULL;
3444 }
3445 cur_pos++;
3446 PARSE_ONE_ELEMENT(')');
3447 imap_flags = imap_parse_flags(buf);
3448 } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) {
3449 cur_pos += 12;
3450 size = strtol(cur_pos, &cur_pos, 10);
3451 } else if (!strncmp(cur_pos, "RFC822.HEADER", 13)) {
3452 gchar *headers;
3453
3454 cur_pos += 13;
3455 cur_pos = imap_get_header(session, cur_pos, &headers,
3456 line_str);
3457 if (cur_pos == NULL) {
3458 g_warning("RFC822.HEADER: cur_pos == NULL\n");
3459 procmsg_msginfo_free(msginfo);
3460 return NULL;
3461 }
3462 if (!msginfo)
3463 msginfo = procheader_parse_str(headers, flags, FALSE);
3464 g_free(headers);
3465 } else {
3466 g_warning("invalid FETCH response: %s\n", cur_pos);
3467 break;
3468 }
3469 }
3470
3471#undef PARSE_ONE_ELEMENT
3472
3473 if (msginfo) {
3474 msginfo->msgnum = uid;
3475 msginfo->size = size;
3476 msginfo->flags.tmp_flags |= imap_flags.tmp_flags;
3477 msginfo->flags.perm_flags = imap_flags.perm_flags;
3478 }
3479
3480 return msginfo;
3481}
3482
3483static gint imap_msg_list_change_perm_flags(GSList *msglist, MsgPermFlags flags,
3484 gboolean is_set)
3485{
3486 Folder *folder;
3487 IMAPSession *session;
3488 IMAPFlags iflags = 0;
3489 MsgInfo *msginfo;
3490 GSList *seq_list, *cur;
3491 gint ok = IMAP_SUCCESS;
3492
3493 if (msglist == NULL) return IMAP_SUCCESS;
3494
3495 msginfo = (MsgInfo *)msglist->data;
3496 g_return_val_if_fail(msginfo != NULL, -1);
3497
3498 g_return_val_if_fail(MSG_IS_IMAP(msginfo->flags), -1);
3499 g_return_val_if_fail(msginfo->folder != NULL, -1);
3500 g_return_val_if_fail(msginfo->folder->folder != NULL, -1);
3501
3502 folder = msginfo->folder->folder;
3503 g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1);
3504
3505 session = imap_session_get(folder);
3506 if (!session) return -1;
3507
3508 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3509 NULL, NULL, NULL, NULL);
3510 if (ok != IMAP_SUCCESS)
3511 return ok;
3512
3513 seq_list = imap_get_seq_set_from_msglist(msglist, 0);
3514
3515 if (flags & MSG_MARKED) iflags |= IMAP_FLAG_FLAGGED;
3516 if (flags & MSG_REPLIED) iflags |= IMAP_FLAG_ANSWERED;
3517
3518 for (cur = seq_list; cur != NULL; cur = cur->next) {
3519 gchar *seq_set = (gchar *)cur->data;
3520
3521 if (iflags) {
3522 ok = imap_set_message_flags(session, seq_set, iflags,
3523 is_set);
3524 if (ok != IMAP_SUCCESS) break;
3525 }
3526
3527 if (flags & MSG_UNREAD) {
3528 ok = imap_set_message_flags(session, seq_set,
3529 IMAP_FLAG_SEEN, !is_set);
3530 if (ok != IMAP_SUCCESS) break;
3531 }
3532 }
3533
3534 imap_seq_set_free(seq_list);
3535
3536 return ok;
3537}
3538
3539gint imap_msg_set_perm_flags(MsgInfo *msginfo, MsgPermFlags flags)
3540{
3541 GSList msglist;
3542
3543 msglist.data = msginfo;
3544 msglist.next = NULL;
3545
3546 return imap_msg_list_change_perm_flags(&msglist, flags, TRUE);
3547}
3548
3549gint imap_msg_unset_perm_flags(MsgInfo *msginfo, MsgPermFlags flags)
3550{
3551 GSList msglist;
3552
3553 msglist.data = msginfo;
3554 msglist.next = NULL;
3555
3556 return imap_msg_list_change_perm_flags(&msglist, flags, FALSE);
3557}
3558
3559gint imap_msg_list_set_perm_flags(GSList *msglist, MsgPermFlags flags)
3560{
3561 return imap_msg_list_change_perm_flags(msglist, flags, TRUE);
3562}
3563
3564gint imap_msg_list_unset_perm_flags(GSList *msglist, MsgPermFlags flags)
3565{
3566 return imap_msg_list_change_perm_flags(msglist, flags, FALSE);
3567}
3568
3569gint imap_msg_list_set_colorlabel_flags(GSList *msglist, guint color)
3570{
3571 Folder *folder;
3572 IMAPSession *session;
3573 IMAPFlags iflags = 0;
3574 MsgInfo *msginfo;
3575 GSList *seq_list, *cur;
3576 gint ok = IMAP_SUCCESS;
3577
3578 if (msglist == NULL) return IMAP_SUCCESS;
3579
3580 msginfo = (MsgInfo *)msglist->data;
3581 g_return_val_if_fail(msginfo != NULL, -1);
3582
3583 g_return_val_if_fail(MSG_IS_IMAP(msginfo->flags), -1);
3584 g_return_val_if_fail(msginfo->folder != NULL, -1);
3585 g_return_val_if_fail(msginfo->folder->folder != NULL, -1);
3586
3587 folder = msginfo->folder->folder;
3588 g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1);
3589
3590 session = imap_session_get(folder);
3591 if (!session) return -1;
3592
3593 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3594 NULL, NULL, NULL, NULL);
3595 if (ok != IMAP_SUCCESS)
3596 return ok;
3597
3598 seq_list = imap_get_seq_set_from_msglist(msglist, 0);
3599
3600 IMAP_SET_COLORLABEL_VALUE(iflags, color);
3601
3602 for (cur = seq_list; cur != NULL; cur = cur->next) {
3603 gchar *seq_set = (gchar *)cur->data;
3604
3605 ok = imap_cmd_store(session, seq_set,
3606 "-FLAGS.SILENT ($label1 $label2 $label3 $label4 $label5 $label6 $label7)");
3607 if (ok != IMAP_SUCCESS) break;
3608
3609 if (iflags) {
3610 ok = imap_set_message_flags(session, seq_set, iflags,
3611 TRUE);
3612 if (ok != IMAP_SUCCESS) break;
3613 }
3614 }
3615
3616 imap_seq_set_free(seq_list);
3617
3618 return ok;
3619}
3620
3621static gchar *imap_get_flag_str(IMAPFlags flags)
3622{
3623 GString *str;
3624 gchar *ret;
3625 guint color;
3626
3627 str = g_string_new(NULL);
3628
3629 if (IMAP_IS_SEEN(flags)) g_string_append(str, "\\Seen ");
3630 if (IMAP_IS_ANSWERED(flags)) g_string_append(str, "\\Answered ");
3631 if (IMAP_IS_FLAGGED(flags)) g_string_append(str, "\\Flagged ");
3632 if (IMAP_IS_DELETED(flags)) g_string_append(str, "\\Deleted ");
3633 if (IMAP_IS_DRAFT(flags)) g_string_append(str, "\\Draft ");
3634
3635 if ((color = IMAP_GET_COLORLABEL_VALUE(flags)) != 0) {
3636 g_string_append_printf(str, "$label%u", color);
3637 }
3638
3639 if (str->len > 0 && str->str[str->len - 1] == ' ')
3640 g_string_truncate(str, str->len - 1);
3641
3642 ret = str->str;
3643 g_string_free(str, FALSE);
3644
3645 return ret;
3646}
3647
3648static gint imap_set_message_flags(IMAPSession *session,
3649 const gchar *seq_set,
3650 IMAPFlags flags,
3651 gboolean is_set)
3652{
3653 gchar *cmd;
3654 gchar *flag_str;
3655 gint ok;
3656
3657 flag_str = imap_get_flag_str(flags);
3658 cmd = g_strconcat(is_set ? "+FLAGS.SILENT (" : "-FLAGS.SILENT (",
3659 flag_str, ")", NULL);
3660 g_free(flag_str);
3661
3662 ok = imap_cmd_store(session, seq_set, cmd);
3663 g_free(cmd);
3664
3665 return ok;
3666}
3667
3668static gint imap_select(IMAPSession *session, IMAPFolder *folder,
3669 const gchar *path,
3670 gint *exists, gint *recent, gint *unseen,
3671 guint32 *uid_validity)
3672{
3673 gchar *real_path;
3674 gint ok;
3675 gint exists_, recent_, unseen_;
3676 guint32 uid_validity_;
3677
3678 if (!exists || !recent || !unseen || !uid_validity) {
3679 if (session->mbox && strcmp(session->mbox, path) == 0)
3680 return IMAP_SUCCESS;
3681 exists = &exists_;
3682 recent = &recent_;
3683 unseen = &unseen_;
3684 uid_validity = &uid_validity_;
3685 }
3686
3687 g_free(session->mbox);
3688 session->mbox = NULL;
3689
3690 real_path = imap_get_real_path(folder, path);
3691 ok = imap_cmd_select(session, real_path,
3692 exists, recent, unseen, uid_validity);
3693 if (ok != IMAP_SUCCESS)
3694 log_warning(_("can't select folder: %s\n"), real_path);
3695 else
3696 session->mbox = g_strdup(path);
3697 g_free(real_path);
3698
3699 return ok;
3700}
3701
3702#define THROW(err) { ok = err; goto catch; }
3703
3704static gint imap_status(IMAPSession *session, IMAPFolder *folder,
3705 const gchar *path,
3706 gint *messages, gint *recent,
3707 guint32 *uid_next, guint32 *uid_validity,
3708 gint *unseen)
3709{
3710 gchar *real_path;
3711 gchar *real_path_;
3712 gint ok;
3713 GPtrArray *argbuf = NULL;
3714 gchar *str;
3715
3716 if (messages && recent && uid_next && uid_validity && unseen) {
3717 *messages = *recent = *uid_next = *uid_validity = *unseen = 0;
3718 argbuf = g_ptr_array_new();
3719 }
3720
3721 real_path = imap_get_real_path(folder, path);
3722 QUOTE_IF_REQUIRED(real_path_, real_path);
3723 ok = imap_cmd_gen_send(session, "STATUS %s "
3724 "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)",
3725 real_path_);
3726 if (ok != IMAP_SUCCESS) {
3727 log_warning("error on sending imap command: STATUS\n");
3728 THROW(ok);
3729 }
3730 ok = imap_cmd_ok(session, argbuf);
3731 if (ok != IMAP_SUCCESS)
3732 log_warning(_("error on imap command: STATUS\n"));
3733 if (ok != IMAP_SUCCESS || !argbuf) THROW(ok);
3734
3735 str = search_array_str(argbuf, "STATUS");
3736 if (!str) THROW(IMAP_ERROR);
3737
3738 str = strrchr_with_skip_quote(str, '"', '(');
3739 if (!str) THROW(IMAP_ERROR);
3740 str++;
3741 while (*str != '\0' && *str != ')') {
3742 while (*str == ' ') str++;
3743
3744 if (!strncmp(str, "MESSAGES ", 9)) {
3745 str += 9;
3746 *messages = strtol(str, &str, 10);
3747 } else if (!strncmp(str, "RECENT ", 7)) {
3748 str += 7;
3749 *recent = strtol(str, &str, 10);
3750 } else if (!strncmp(str, "UIDNEXT ", 8)) {
3751 str += 8;
3752 *uid_next = strtoul(str, &str, 10);
3753 } else if (!strncmp(str, "UIDVALIDITY ", 12)) {
3754 str += 12;
3755 *uid_validity = strtoul(str, &str, 10);
3756 } else if (!strncmp(str, "UNSEEN ", 7)) {
3757 str += 7;
3758 *unseen = strtol(str, &str, 10);
3759 } else {
3760 g_warning("invalid STATUS response: %s\n", str);
3761 break;
3762 }
3763 }
3764
3765catch:
3766 g_free(real_path);
3767 if (argbuf) {
3768 ptr_array_free_strings(argbuf);
3769 g_ptr_array_free(argbuf, TRUE);
3770 }
3771
3772 return ok;
3773}
3774
3775#undef THROW
3776
3777static gboolean imap_has_capability(IMAPSession *session,
3778 const gchar *capability)
3779{
3780 gchar **p;
3781
3782 for (p = session->capability; *p != NULL; ++p) {
3783 if (!g_ascii_strcasecmp(*p, capability))
3784 return TRUE;
3785 }
3786
3787 return FALSE;
3788}
3789
3790static void imap_capability_free(IMAPSession *session)
3791{
3792 if (session->capability) {
3793 g_strfreev(session->capability);
3794 session->capability = NULL;
3795 }
3796}
3797
3798
3799/* low-level IMAP4rev1 commands */
3800
3801#define THROW(err) { ok = err; goto catch; }
3802
3803static gint imap_cmd_capability(IMAPSession *session)
3804{
3805 gint ok;
3806 GPtrArray *argbuf;
3807 gchar *capability;
3808
3809 argbuf = g_ptr_array_new();
3810
3811 if ((ok = imap_cmd_gen_send(session, "CAPABILITY")) != IMAP_SUCCESS)
3812 THROW(ok);
3813 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
3814
3815 capability = search_array_str(argbuf, "CAPABILITY ");
3816 if (!capability) THROW(IMAP_ERROR);
3817
3818 capability += strlen("CAPABILITY ");
3819
3820 imap_capability_free(session);
3821 session->capability = g_strsplit(capability, " ", -1);
3822
3823catch:
3824 ptr_array_free_strings(argbuf);
3825 g_ptr_array_free(argbuf, TRUE);
3826
3827 return ok;
3828}
3829
3830#undef THROW
3831
3832static gint imap_cmd_auth_plain(IMAPSession *session, const gchar *user,
3833 const gchar *pass)
3834{
3835 gchar *p;
3836 gchar *response;
3837 gchar *response64;
3838 gint ok;
3839
3840 p = response = g_malloc(strlen(user) * 2 + 2 + strlen(pass) + 1);
3841 strcpy(p, user);
3842 p += strlen(user) + 1;
3843 strcpy(p, user);
3844 p += strlen(user) + 1;
3845 strcpy(p, pass);
3846 p += strlen(pass);
3847
3848 response64 = g_malloc((p - response) * 2 + 1);
3849 base64_encode(response64, (guchar *)response, p - response);
3850 g_free(response);
3851
3852 log_print("IMAP4> ****************\n");
3853 sock_puts(SESSION(session)->sock, response64);
3854 ok = imap_cmd_ok(session, NULL);
3855 if (ok != IMAP_SUCCESS)
3856 log_warning(_("IMAP4 authentication failed.\n"));
3857 g_free(response64);
3858
3859 return ok;
3860}
3861
3862static gint imap_cmd_auth_cram_md5(IMAPSession *session, const gchar *user,
3863 const gchar *pass, const gchar *challenge64)
3864{
3865 gchar *challenge;
3866 gint challenge_len;
3867 gchar hexdigest[33];
3868 gchar *response;
3869 gchar *response64;
3870 gint ok;
3871
3872 challenge = g_malloc(strlen(challenge64 + 2) + 1);
3873 challenge_len = base64_decode((guchar *)challenge, challenge64 + 2, -1);
3874 challenge[challenge_len] = '\0';
3875 log_print("IMAP< [Decoded: %s]\n", challenge);
3876
3877 md5_hex_hmac(hexdigest, (guchar *)challenge, challenge_len,
3878 (guchar *)pass, strlen(pass));
3879 g_free(challenge);
3880
3881 response = g_strdup_printf("%s %s", user, hexdigest);
3882 log_print("IMAP> [Encoded: %s]\n", response);
3883 response64 = g_malloc((strlen(response) + 3) * 2 + 1);
3884 base64_encode(response64, (guchar *)response, strlen(response));
3885 g_free(response);
3886
3887 log_print("IMAP> %s\n", response64);
3888 sock_puts(SESSION(session)->sock, response64);
3889 ok = imap_cmd_ok(session, NULL);
3890 if (ok != IMAP_SUCCESS)
3891 log_warning(_("IMAP4 authentication failed.\n"));
3892
3893 return ok;
3894}
3895
3896static gint imap_cmd_authenticate(IMAPSession *session, const gchar *user,
3897 const gchar *pass, IMAPAuthType type)
3898{
3899 gchar *auth_type;
3900 gint ok;
3901 gchar *buf = NULL;
3902
3903 g_return_val_if_fail((type == 0 || type == IMAP_AUTH_CRAM_MD5 ||
3904 type == IMAP_AUTH_PLAIN), IMAP_ERROR);
3905
3906 if (type == IMAP_AUTH_PLAIN)
3907 auth_type = "PLAIN";
3908 else
3909 auth_type = "CRAM-MD5";
3910
3911 ok = imap_cmd_gen_send(session, "AUTHENTICATE %s", auth_type);
3912 if (ok != IMAP_SUCCESS) {
3913 g_free(buf);
3914 return ok;
3915 }
3916 ok = imap_cmd_gen_recv(session, &buf);
3917 if (ok != IMAP_SUCCESS || buf[0] != '+') {
3918 g_free(buf);
3919 return IMAP_ERROR;
3920 }
3921
3922 if (type == IMAP_AUTH_PLAIN)
3923 ok = imap_cmd_auth_plain(session, user, pass);
3924 else
3925 ok = imap_cmd_auth_cram_md5(session, user, pass, buf);
3926
3927 g_free(buf);
3928
3929 return ok;
3930}
3931
3932static gint imap_cmd_login(IMAPSession *session,
3933 const gchar *user, const gchar *pass)
3934{
3935 gchar *user_, *pass_;
3936 gint ok;
3937
3938 QUOTE_IF_REQUIRED(user_, user);
3939 QUOTE_IF_REQUIRED(pass_, pass);
3940 ok = imap_cmd_gen_send(session, "LOGIN %s %s", user_, pass_);
3941 if (ok == IMAP_SUCCESS)
3942 ok = imap_cmd_ok(session, NULL);
3943 if (ok != IMAP_SUCCESS)
3944 log_warning(_("IMAP4 login failed.\n"));
3945
3946 return ok;
3947}
3948
3949static gint imap_cmd_logout(IMAPSession *session)
3950{
3951 if (imap_cmd_gen_send(session, "LOGOUT") != IMAP_SUCCESS)
3952 return IMAP_ERROR;
3953 return imap_cmd_ok(session, NULL);
3954}
3955
3956static gint imap_cmd_noop(IMAPSession *session)
3957{
3958 gint ret;
3959
3960 ret = imap_cmd_gen_send(session, "NOOP");
3961 if (ret != IMAP_SUCCESS)
3962 return ret;
3963 return imap_cmd_ok(session, NULL);
3964}
3965
3966#if USE_SSL
3967static gint imap_cmd_starttls(IMAPSession *session)
3968{
3969 if (imap_cmd_gen_send(session, "STARTTLS") != IMAP_SUCCESS)
3970 return IMAP_ERROR;
3971 return imap_cmd_ok(session, NULL);
3972}
3973#endif
3974
3975#define THROW(err) { ok = err; goto catch; }
3976
3977static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str)
3978{
3979 gint ok;
3980 GPtrArray *argbuf;
3981 gchar *str;
3982
3983 argbuf = g_ptr_array_new();
3984
3985 if ((ok = imap_cmd_gen_send(session, "NAMESPACE")) != IMAP_SUCCESS)
3986 THROW(ok);
3987 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
3988
3989 str = search_array_str(argbuf, "NAMESPACE");
3990 if (!str) THROW(IMAP_ERROR);
3991
3992 *ns_str = g_strdup(str);
3993
3994catch:
3995 ptr_array_free_strings(argbuf);
3996 g_ptr_array_free(argbuf, TRUE);
3997
3998 return ok;
3999}
4000
4001#undef THROW
4002
4003static gint imap_cmd_list(IMAPSession *session, const gchar *ref,
4004 const gchar *mailbox, GPtrArray *argbuf)
4005{
4006 gchar *ref_, *mailbox_;
4007
4008 if (!ref) ref = "";
4009 if (!mailbox) mailbox = "";
4010
4011 QUOTE_IF_REQUIRED(ref_, ref);
4012 QUOTE_IF_REQUIRED(mailbox_, mailbox);
4013 if (imap_cmd_gen_send(session, "LIST %s %s", ref_, mailbox_) != IMAP_SUCCESS)
4014 return IMAP_ERROR;
4015
4016 return imap_cmd_ok(session, argbuf);
4017}
4018
4019#define THROW goto catch
4020
4021static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder,
4022 gboolean examine,
4023 gint *exists, gint *recent, gint *unseen,
4024 guint32 *uid_validity)
4025{
4026 gint ok;
4027 gchar *resp_str;
4028 GPtrArray *argbuf;
4029 gchar *select_cmd;
4030 gchar *folder_;
4031 guint uid_validity_;
4032
4033 *exists = *recent = *unseen = *uid_validity = 0;
4034 argbuf = g_ptr_array_new();
4035
4036 if (examine)
4037 select_cmd = "EXAMINE";
4038 else
4039 select_cmd = "SELECT";
4040
4041 QUOTE_IF_REQUIRED(folder_, folder);
4042 if ((ok = imap_cmd_gen_send(session, "%s %s", select_cmd, folder_)) != IMAP_SUCCESS)
4043 THROW;
4044
4045 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW;
4046
4047 resp_str = search_array_contain_str(argbuf, "EXISTS");
4048 if (resp_str) {
4049 if (sscanf(resp_str,"%d EXISTS", exists) != 1) {
4050 g_warning("imap_cmd_select(): invalid EXISTS line.\n");
4051 THROW;
4052 }
4053 }
4054
4055 resp_str = search_array_contain_str(argbuf, "RECENT");
4056 if (resp_str) {
4057 if (sscanf(resp_str, "%d RECENT", recent) != 1) {
4058 g_warning("imap_cmd_select(): invalid RECENT line.\n");
4059 THROW;
4060 }
4061 }
4062
4063 resp_str = search_array_contain_str(argbuf, "UIDVALIDITY");
4064 if (resp_str) {
4065 if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", &uid_validity_)
4066 != 1) {
4067 g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n");
4068 THROW;
4069 }
4070 *uid_validity = uid_validity_;
4071 }
4072
4073 resp_str = search_array_contain_str(argbuf, "UNSEEN");
4074 if (resp_str) {
4075 if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) {
4076 g_warning("imap_cmd_select(): invalid UNSEEN line.\n");
4077 THROW;
4078 }
4079 }
4080
4081catch:
4082 ptr_array_free_strings(argbuf);
4083 g_ptr_array_free(argbuf, TRUE);
4084
4085 return ok;
4086}
4087
4088static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
4089 gint *exists, gint *recent, gint *unseen,
4090 guint32 *uid_validity)
4091{
4092 return imap_cmd_do_select(session, folder, FALSE,
4093 exists, recent, unseen, uid_validity);
4094}
4095
4096static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
4097 gint *exists, gint *recent, gint *unseen,
4098 guint32 *uid_validity)
4099{
4100 return imap_cmd_do_select(session, folder, TRUE,
4101 exists, recent, unseen, uid_validity);
4102}
4103
4104#undef THROW
4105
4106static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
4107{
4108 gchar *folder_;
4109
4110 QUOTE_IF_REQUIRED(folder_, folder);
4111 if (imap_cmd_gen_send(session, "CREATE %s", folder_) != IMAP_SUCCESS)
4112 return IMAP_ERROR;
4113
4114 return imap_cmd_ok(session, NULL);
4115}
4116
4117static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
4118 const gchar *new_folder)
4119{
4120 gchar *old_folder_, *new_folder_;
4121
4122 QUOTE_IF_REQUIRED(old_folder_, old_folder);
4123 QUOTE_IF_REQUIRED(new_folder_, new_folder);
4124 if (imap_cmd_gen_send(session, "RENAME %s %s", old_folder_, new_folder_) != IMAP_SUCCESS)
4125 return IMAP_ERROR;
4126
4127 return imap_cmd_ok(session, NULL);
4128}
4129
4130static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
4131{
4132 gchar *folder_;
4133
4134 QUOTE_IF_REQUIRED(folder_, folder);
4135 if (imap_cmd_gen_send(session, "DELETE %s", folder_) != IMAP_SUCCESS)
4136 return IMAP_ERROR;
4137
4138 return imap_cmd_ok(session, NULL);
4139}
4140
4141static gint imap_cmd_subscribe(IMAPSession *session, const gchar *folder)
4142{
4143 gchar *folder_;
4144
4145 QUOTE_IF_REQUIRED(folder_, folder);
4146 if (imap_cmd_gen_send(session, "SUBSCRIBE %s", folder_) != IMAP_SUCCESS)
4147 return IMAP_ERROR;
4148
4149 return imap_cmd_ok(session, NULL);
4150}
4151
4152#define THROW(err) { ok = err; goto catch; }
4153
4154static gint imap_cmd_search(IMAPSession *session, const gchar *criteria,
4155 GArray **result)
4156{
4157 gint ok;
4158 GPtrArray *argbuf;
4159 GArray *array;
4160 gchar *str;
4161 gchar *p, *ep;
4162 gint i;
4163 guint32 uid;
4164
4165 g_return_val_if_fail(criteria != NULL, IMAP_ERROR);
4166 g_return_val_if_fail(result != NULL, IMAP_ERROR);
4167
4168 argbuf = g_ptr_array_new();
4169
4170 if ((ok = imap_cmd_gen_send(session, "UID SEARCH %s", criteria)) != IMAP_SUCCESS)
4171 THROW(ok);
4172 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
4173
4174 array = g_array_new(FALSE, FALSE, sizeof(guint32));
4175
4176 for (i = 0; i < argbuf->len; i++) {
4177 str = g_ptr_array_index(argbuf, i);
4178 if (strncmp(str, "SEARCH", 6) != 0)
4179 continue;
4180
4181 p = str + 6;
4182 while (*p != '\0') {
4183 uid = strtoul(p, &ep, 10);
4184 if (p < ep && uid > 0) {
4185 g_array_append_val(array, uid);
4186 p = ep;
4187 } else
4188 break;
4189 }
4190 }
4191
4192 *result = array;
4193
4194catch:
4195 ptr_array_free_strings(argbuf);
4196 g_ptr_array_free(argbuf, TRUE);
4197
4198 return ok;
4199}
4200
4201typedef struct _IMAPCmdFetchData
4202{
4203 guint32 uid;
4204 const gchar *filename;
4205} IMAPCmdFetchData;
4206
4207#define THROW(err) { ok = err; goto catch; }
4208
4209static gint imap_cmd_fetch_func(IMAPSession *session, gpointer data)
4210{
4211 const gchar *filename = ((IMAPCmdFetchData *)data)->filename;
4212 gint ok;
4213 gchar *buf;
4214 gchar *cur_pos;
4215 gchar size_str[32];
4216 glong size_num;
4217 gint ret;
4218
4219 while ((ok = imap_cmd_gen_recv(session, &buf)) == IMAP_SUCCESS) {
4220 if (buf[0] != '*' || buf[1] != ' ') {
4221 g_free(buf);
4222 return IMAP_ERROR;
4223 }
4224 if (strstr(buf, "FETCH") != NULL && strstr(buf, "BODY") != NULL)
4225 break;
4226 g_free(buf);
4227 }
4228 if (ok != IMAP_SUCCESS)
4229 THROW(ok);
4230
4231#define RETURN_ERROR_IF_FAIL(cond) \
4232 if (!(cond)) { \
4233 g_free(buf); \
4234 ok = imap_cmd_ok_real(session, NULL); \
4235 THROW(IMAP_ERROR); \
4236 }
4237
4238 cur_pos = strchr(buf, '{');
4239 RETURN_ERROR_IF_FAIL(cur_pos != NULL);
4240 cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
4241 RETURN_ERROR_IF_FAIL(cur_pos != NULL);
4242 size_num = atol(size_str);
4243 RETURN_ERROR_IF_FAIL(size_num >= 0);
4244
4245 RETURN_ERROR_IF_FAIL(*cur_pos == '\0');
4246
4247#undef RETURN_ERROR_IF_FAIL
4248
4249 g_free(buf);
4250
4251 if ((ret = recv_bytes_write_to_file(SESSION(session)->sock,
4252 size_num, filename)) != 0) {
4253 if (ret == -2)
4254 THROW(IMAP_SOCKET);
4255 }
4256
4257 if (imap_cmd_gen_recv(session, &buf) != IMAP_SUCCESS)
4258 THROW(IMAP_ERROR);
4259
4260 if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') {
4261 g_free(buf);
4262 THROW(IMAP_ERROR);
4263 }
4264 g_free(buf);
4265
4266 ok = imap_cmd_ok_real(session, NULL);
4267
4268 if (ret != 0)
4269 THROW(IMAP_ERROR);
4270
4271catch:
4272 return ok;
4273}
4274
4275#undef THROW
4276
4277static gint imap_cmd_fetch(IMAPSession *session, guint32 uid,
4278 const gchar *filename)
4279{
4280 gint ok;
4281 IMAPCmdFetchData fetch_data = {uid, filename};
4282
4283 g_return_val_if_fail(filename != NULL, IMAP_ERROR);
4284
4285 ok = imap_cmd_gen_send(session, "UID FETCH %u BODY.PEEK[]", uid);
4286 if (ok != IMAP_SUCCESS)
4287 return ok;
4288
4289#if USE_THREADS
4290 ok = imap_thread_run(session, imap_cmd_fetch_func, &fetch_data);
4291#else
4292 ok = imap_cmd_fetch_func(session, &fetch_data);
4293#endif
4294
4295 return ok;
4296}
4297
4298static void imap_get_date_time(gchar *buf, size_t len, stime_t timer)
4299{
4300 static gchar monthstr[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
4301 struct tm *lt;
4302 gchar date_time[64];
4303 gchar tz[6];
4304 time_t timer_ = timer;
4305
4306 lt = localtime(&timer_);
4307 if (lt && lt->tm_mon >= 0 && lt->tm_mon < 12) {
4308 strftime(date_time, sizeof(date_time), "%d-___-%Y %H:%M:%S",
4309 lt);
4310 tzoffset_buf(tz, &timer);
4311 memcpy(date_time + 3, monthstr + lt->tm_mon * 3, 3);
4312 g_snprintf(buf, len, "%s %s", date_time, tz);
4313 }
4314}
4315
4316static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
4317 const gchar *file, IMAPFlags flags,
4318 guint32 *new_uid)
4319{
4320 gint ok;
4321 MsgInfo *msginfo;
4322 MsgFlags flags_ = {0, 0};
4323 gchar date_time[64] = "";
4324 gint size;
4325 gchar *destfolder_;
4326 gchar *flag_str;
4327 guint new_uid_;
4328 gchar *ret = NULL;
4329 gchar buf[BUFFSIZE];
4330 FILE *fp;
4331 FILE *tmp;
4332 size_t read_len;
4333 GPtrArray *argbuf;
4334 gchar *resp_str;
4335
4336 g_return_val_if_fail(file != NULL, IMAP_ERROR);
4337
4338 if ((fp = g_fopen(file, "rb")) == NULL) {
4339 FILE_OP_ERROR(file, "fopen");
4340 return -1;
4341 }
4342
4343 /* use Date: header as received date */
4344 msginfo = procheader_parse_stream(fp, flags_, FALSE);
4345 imap_get_date_time(date_time, sizeof(date_time), msginfo->date_t);
4346 procmsg_msginfo_free(msginfo);
4347
4348 rewind(fp);
4349 tmp = canonicalize_file_stream(fp, &size);
4350 fclose(fp);
4351 if (!tmp)
4352 return -1;
4353
4354 QUOTE_IF_REQUIRED(destfolder_, destfolder);
4355 flag_str = imap_get_flag_str(flags);
4356 if (date_time[0])
4357 ok = imap_cmd_gen_send(session, "APPEND %s (%s) \"%s\" {%d}",
4358 destfolder_, flag_str, date_time, size);
4359 else
4360 ok = imap_cmd_gen_send(session, "APPEND %s (%s) {%d}",
4361 destfolder_, flag_str, size);
4362 g_free(flag_str);
4363 if (ok != IMAP_SUCCESS) {
4364 log_warning(_("can't append %s to %s\n"), file, destfolder_);
4365 fclose(tmp);
4366 return ok;
4367 }
4368
4369 ok = imap_cmd_gen_recv(session, &ret);
4370 if (ok != IMAP_SUCCESS || ret[0] != '+') {
4371 log_warning(_("can't append %s to %s\n"), file, destfolder_);
4372 g_free(ret);
4373 fclose(tmp);
4374 return IMAP_ERROR;
4375 }
4376 g_free(ret);
4377
4378 log_print("IMAP4> %s\n", _("(sending file...)"));
4379
4380 while ((read_len = fread(buf, 1, sizeof(buf), tmp)) > 0) {
4381 if (read_len < sizeof(buf) && ferror(tmp))
4382 break;
4383 if (sock_write_all(SESSION(session)->sock, buf, read_len) < 0) {
4384 fclose(tmp);
4385 return -1;
4386 }
4387 }
4388
4389 if (ferror(tmp)) {
4390 FILE_OP_ERROR(file, "fread");
4391 fclose(tmp);
4392 return -1;
4393 }
4394
4395 sock_puts(SESSION(session)->sock, "");
4396
4397 fclose(tmp);
4398
4399 if (new_uid != NULL)
4400 *new_uid = 0;
4401
4402 if (new_uid != NULL && session->uidplus) {
4403 argbuf = g_ptr_array_new();
4404
4405 ok = imap_cmd_ok(session, argbuf);
4406 if (ok != IMAP_SUCCESS)
4407 log_warning(_("can't append message to %s\n"),
4408 destfolder_);
4409 else if (argbuf->len > 0) {
4410 resp_str = g_ptr_array_index(argbuf, argbuf->len - 1);
4411 if (resp_str &&
4412 sscanf(resp_str, "%*u OK [APPENDUID %*u %u]",
4413 &new_uid_) == 1) {
4414 *new_uid = new_uid_;
4415 }
4416 }
4417
4418 ptr_array_free_strings(argbuf);
4419 g_ptr_array_free(argbuf, TRUE);
4420 } else
4421 ok = imap_cmd_ok(session, NULL);
4422
4423 return ok;
4424}
4425
4426static gint imap_cmd_copy(IMAPSession *session, const gchar *seq_set,
4427 const gchar *destfolder)
4428{
4429 gint ok;
4430 gchar *destfolder_;
4431
4432 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
4433
4434 QUOTE_IF_REQUIRED(destfolder_, destfolder);
4435 ok = imap_cmd_gen_send(session, "UID COPY %s %s", seq_set, destfolder_);
4436 if (ok == IMAP_SUCCESS)
4437 ok = imap_cmd_ok(session, NULL);
4438 if (ok != IMAP_SUCCESS) {
4439 log_warning(_("can't copy %s to %s\n"), seq_set, destfolder_);
4440 return -1;
4441 }
4442
4443 return ok;
4444}
4445
4446gint imap_cmd_envelope(IMAPSession *session, const gchar *seq_set)
4447{
4448 return imap_cmd_gen_send
4449 (session, "UID FETCH %s (UID FLAGS RFC822.SIZE RFC822.HEADER)",
4450 seq_set);
4451}
4452
4453static gint imap_cmd_store(IMAPSession *session, const gchar *seq_set,
4454 const gchar *sub_cmd)
4455{
4456 gint ok;
4457
4458 ok = imap_cmd_gen_send(session, "UID STORE %s %s", seq_set, sub_cmd);
4459 if (ok == IMAP_SUCCESS)
4460 ok = imap_cmd_ok(session, NULL);
4461 if (ok != IMAP_SUCCESS) {
4462 log_warning(_("error while imap command: STORE %s %s\n"),
4463 seq_set, sub_cmd);
4464 return ok;
4465 }
4466
4467 return IMAP_SUCCESS;
4468}
4469
4470static gint imap_cmd_expunge(IMAPSession *session)
4471{
4472 gint ok;
4473
4474 ok = imap_cmd_gen_send(session, "EXPUNGE");
4475 if (ok == IMAP_SUCCESS)
4476 ok = imap_cmd_ok(session, NULL);
4477 if (ok != IMAP_SUCCESS) {
4478 log_warning(_("error while imap command: EXPUNGE\n"));
4479 return ok;
4480 }
4481
4482 return IMAP_SUCCESS;
4483}
4484
4485static gint imap_cmd_close(IMAPSession *session)
4486{
4487 gint ok;
4488
4489 ok = imap_cmd_gen_send(session, "CLOSE");
4490 if (ok == IMAP_SUCCESS)
4491 ok = imap_cmd_ok(session, NULL);
4492 if (ok != IMAP_SUCCESS)
4493 log_warning(_("error while imap command: CLOSE\n"));
4494
4495 return ok;
4496}
4497
4498static gint imap_cmd_ok_real(IMAPSession *session, GPtrArray *argbuf)
4499{
4500 gint ok;
4501 gchar *buf;
4502 gint cmd_num;
4503 gchar cmd_status[IMAPBUFSIZE + 1];
4504 GString *str;
4505 gchar *p;
4506 gchar obuf[32];
4507 gint len;
4508 gchar *literal;
4509
4510 str = g_string_sized_new(256);
4511
4512 //g_usleep(800000);
4513 while ((ok = imap_cmd_gen_recv(session, &buf)) == IMAP_SUCCESS) {
4514 g_string_append(str, buf);
4515
4516 if ((p = strrchr_with_skip_quote(buf, '"', '{'))) {
4517 /* literal */
4518 p = strchr_cpy(p + 1, '}', obuf, sizeof(obuf));
4519 len = atoi(obuf);
4520 if (len < 0 || p == NULL || *p != '\0') {
4521 g_free(buf);
4522 ok = IMAP_ERROR;
4523 break;
4524 }
4525
4526 literal = recv_bytes(SESSION(session)->sock, len);
4527 if (!literal) {
4528 g_free(buf);
4529 ok = IMAP_SOCKET;
4530 break;
4531 }
4532 if (memchr(literal, '\n', len))
4533 log_print("IMAP4< (literal: %d bytes)\n", len);
4534 else
4535 log_print("IMAP4< %s\n", literal);
4536
4537 g_string_append(str, "\r\n");
4538 g_string_append_len(str, literal, len);
4539 g_free(literal);
4540 g_free(buf);
4541 continue;
4542 }
4543
4544 g_free(buf);
4545
4546 if (str->str[0] == '*' && str->str[1] == ' ') {
4547 if (argbuf)
4548 g_ptr_array_add(argbuf, g_strdup(str->str + 2));
4549
4550 g_string_truncate(str, 0);
4551 continue;
4552 } else if (sscanf(str->str, "%d %" Xstr(IMAPBUFSIZE) "s",
4553 &cmd_num, cmd_status) < 2) {
4554 ok = IMAP_ERROR;
4555 } else if (cmd_num == session->cmd_count &&
4556 !strcmp(cmd_status, "OK")) {
4557 if (argbuf)
4558 g_ptr_array_add(argbuf, g_strdup(str->str));
4559 } else {
4560 ok = IMAP_ERROR;
4561 }
4562
4563 break;
4564 }
4565
4566 g_string_free(str, TRUE);
4567 return ok;
4568}
4569
4570#if USE_THREADS
4571static gint imap_cmd_ok_func(IMAPSession *session, gpointer data)
4572{
4573 GPtrArray *argbuf = (GPtrArray *)data;
4574 gint ok;
4575
4576 ok = imap_cmd_ok_real(session, argbuf);
4577 return ok;
4578}
4579#endif
4580
4581static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
4582{
4583#if USE_THREADS
4584 return imap_thread_run(session, imap_cmd_ok_func, argbuf);
4585#else
4586 return imap_cmd_ok_real(session, argbuf);
4587#endif
4588}
4589
4590static gint imap_cmd_gen_send(IMAPSession *session, const gchar *format, ...)
4591{
4592 IMAPRealSession *real = (IMAPRealSession *)session;
4593 gchar buf[IMAPBUFSIZE];
4594 gchar tmp[IMAPBUFSIZE];
4595 gchar *p;
4596 va_list args;
4597
4598 va_start(args, format);
4599 g_vsnprintf(tmp, sizeof(tmp), format, args);
4600 va_end(args);
4601
4602#if USE_THREADS
4603 if (real->is_running) {
4604 g_warning("imap_cmd_gen_send: cannot send command because another command is already running.");
4605 return IMAP_EAGAIN;
4606 }
4607#endif
4608
4609 session->cmd_count++;
4610
4611 g_snprintf(buf, sizeof(buf), "%d %s\r\n", session->cmd_count, tmp);
4612 if (!g_ascii_strncasecmp(tmp, "LOGIN ", 6) &&
4613 (p = strchr(tmp + 6, ' '))) {
4614 *p = '\0';
4615 log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp);
4616 } else
4617 log_print("IMAP4> %d %s\n", session->cmd_count, tmp);
4618
4619 sock_write_all(SESSION(session)->sock, buf, strlen(buf));
4620
4621 return IMAP_SUCCESS;
4622}
4623
4624static gint imap_cmd_gen_recv(IMAPSession *session, gchar **ret)
4625{
4626 gint len;
4627
4628 if ((len = sock_getline(SESSION(session)->sock, ret)) < 0)
4629 return IMAP_SOCKET;
4630
4631 strretchomp(*ret);
4632
4633 if (len > 1000) {
4634 gchar *str;
4635
4636 str = trim_string(*ret, 1000);
4637 log_print("IMAP4< %s\n", str);
4638 g_free(str);
4639 } else
4640 log_print("IMAP4< %s\n", *ret);
4641
4642 session_set_access_time(SESSION(session));
4643
4644 return IMAP_SUCCESS;
4645}
4646
4647static gint imap_cmd_gen_recv_silent(IMAPSession *session, gchar **ret)
4648{
4649 gint len;
4650
4651 if ((len = sock_getline(SESSION(session)->sock, ret)) < 0)
4652 return IMAP_SOCKET;
4653
4654 strretchomp(*ret);
4655
4656 session_set_access_time(SESSION(session));
4657
4658 return IMAP_SUCCESS;
4659}
4660
4661
4662/* misc utility functions */
4663
4664static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
4665{
4666 gchar *tmp;
4667
4668 dest[0] = '\0';
4669 tmp = strchr(src, ch);
4670 if (!tmp)
4671 return NULL;
4672
4673 memcpy(dest, src, MIN(tmp - src, len - 1));
4674 dest[MIN(tmp - src, len - 1)] = '\0';
4675
4676 return tmp + 1;
4677}
4678
4679static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
4680{
4681 const gchar *p = src;
4682 gint n = 0;
4683
4684 g_return_val_if_fail(*p == ch, NULL);
4685
4686 *dest = '\0';
4687 p++;
4688
4689 while (*p != '\0' && *p != ch) {
4690 if (n < len - 1) {
4691 if (*p == '\\' && *(p + 1) != '\0')
4692 p++;
4693 *dest++ = *p++;
4694 } else
4695 p++;
4696 n++;
4697 }
4698
4699 *dest = '\0';
4700 return (gchar *)(*p == ch ? p + 1 : p);
4701}
4702
4703static gchar *search_array_contain_str(GPtrArray *array, gchar *str)
4704{
4705 gint i;
4706
4707 for (i = 0; i < array->len; i++) {
4708 gchar *tmp;
4709
4710 tmp = g_ptr_array_index(array, i);
4711 if (strstr(tmp, str) != NULL)
4712 return tmp;
4713 }
4714
4715 return NULL;
4716}
4717
4718static gchar *search_array_str(GPtrArray *array, gchar *str)
4719{
4720 gint i;
4721 gint len;
4722
4723 len = strlen(str);
4724
4725 for (i = 0; i < array->len; i++) {
4726 gchar *tmp;
4727
4728 tmp = g_ptr_array_index(array, i);
4729 if (!strncmp(tmp, str, len))
4730 return tmp;
4731 }
4732
4733 return NULL;
4734}
4735
4736static void imap_path_separator_subst(gchar *str, gchar separator)
4737{
4738 gchar *p;
4739 gboolean in_escape = FALSE;
4740
4741 if (!separator || separator == '/') return;
4742
4743 for (p = str; *p != '\0'; p++) {
4744 if (*p == '/' && !in_escape)
4745 *p = separator;
4746 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
4747 in_escape = TRUE;
4748 else if (*p == '-' && in_escape)
4749 in_escape = FALSE;
4750 }
4751}
4752
4753static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str)
4754{
4755 static iconv_t cd = (iconv_t)-1;
4756 static gboolean iconv_ok = TRUE;
4757 GString *norm_utf7;
4758 gchar *norm_utf7_p;
4759 size_t norm_utf7_len;
4760 const gchar *p;
4761 gchar *to_str, *to_p;
4762 size_t to_len;
4763 gboolean in_escape = FALSE;
4764
4765 if (!iconv_ok) return g_strdup(mutf7_str);
4766
4767 if (cd == (iconv_t)-1) {
4768 cd = iconv_open(CS_INTERNAL, CS_UTF_7);
4769 if (cd == (iconv_t)-1) {
4770 g_warning("iconv cannot convert UTF-7 to %s\n",
4771 CS_INTERNAL);
4772 iconv_ok = FALSE;
4773 return g_strdup(mutf7_str);
4774 }
4775 }
4776
4777 /* modified UTF-7 to normal UTF-7 conversion */
4778 norm_utf7 = g_string_new(NULL);
4779
4780 for (p = mutf7_str; *p != '\0'; p++) {
4781 /* replace: '&' -> '+',
4782 "&-" -> '&',
4783 "+" -> "+-",
4784 escaped ',' -> '/' */
4785 if (!in_escape && *p == '&') {
4786 if (*(p + 1) != '-') {
4787 g_string_append_c(norm_utf7, '+');
4788 in_escape = TRUE;
4789 } else {
4790 g_string_append_c(norm_utf7, '&');
4791 p++;
4792 }
4793 } else if (!in_escape && *p == '+') {
4794 g_string_append(norm_utf7, "+-");
4795 } else if (in_escape && *p == ',') {
4796 g_string_append_c(norm_utf7, '/');
4797 } else if (in_escape && *p == '-') {
4798 g_string_append_c(norm_utf7, '-');
4799 in_escape = FALSE;
4800 } else {
4801 g_string_append_c(norm_utf7, *p);
4802 }
4803 }
4804
4805 /* somehow iconv() returns error when the last of the string is "+-" */
4806 g_string_append_c(norm_utf7, '\n');
4807 norm_utf7_p = norm_utf7->str;
4808 norm_utf7_len = norm_utf7->len;
4809 to_len = strlen(mutf7_str) * 5;
4810 to_p = to_str = g_malloc(to_len + 1);
4811
4812 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
4813 &to_p, &to_len) == -1) {
4814 g_warning(_("iconv cannot convert UTF-7 to %s\n"), CS_INTERNAL);
4815 g_string_free(norm_utf7, TRUE);
4816 g_free(to_str);
4817 return g_strdup(mutf7_str);
4818 }
4819
4820 /* second iconv() call for flushing */
4821 iconv(cd, NULL, NULL, &to_p, &to_len);
4822 g_string_free(norm_utf7, TRUE);
4823 *to_p = '\0';
4824 strretchomp(to_str);
4825
4826 return to_str;
4827}
4828
4829static gchar *imap_utf8_to_modified_utf7(const gchar *from)
4830{
4831 static iconv_t cd = (iconv_t)-1;
4832 static gboolean iconv_ok = TRUE;
4833 gchar *norm_utf7, *norm_utf7_p;
4834 size_t from_len, norm_utf7_len;
4835 GString *to_str;
4836 const gchar *from_tmp;
4837 const gchar *p;
4838 gchar *to;
4839 gboolean in_escape = FALSE;
4840
4841 if (!iconv_ok) return g_strdup(from);
4842
4843 if (cd == (iconv_t)-1) {
4844 cd = iconv_open(CS_UTF_7, CS_INTERNAL);
4845 if (cd == (iconv_t)-1) {
4846 g_warning(_("iconv cannot convert %s to UTF-7\n"),
4847 CS_INTERNAL);
4848 iconv_ok = FALSE;
4849 return g_strdup(from);
4850 }
4851 }
4852
4853 /* UTF-8 to normal UTF-7 conversion */
4854 from_tmp = from;
4855 from_len = strlen(from);
4856 norm_utf7_len = from_len * 5;
4857 norm_utf7 = g_malloc(norm_utf7_len + 1);
4858 norm_utf7_p = norm_utf7;
4859
4860 while (from_len > 0) {
4861 if (*from_tmp == '+') {
4862 *norm_utf7_p++ = '+';
4863 *norm_utf7_p++ = '-';
4864 norm_utf7_len -= 2;
4865 from_tmp++;
4866 from_len--;
4867 } else if (g_ascii_isprint(*from_tmp)) {
4868 /* printable ascii char */
4869 *norm_utf7_p = *from_tmp;
4870 norm_utf7_p++;
4871 norm_utf7_len--;
4872 from_tmp++;
4873 from_len--;
4874 } else {
4875 size_t conv_len = 0;
4876
4877 /* unprintable char: convert to UTF-7 */
4878 p = from_tmp;
4879 while (!g_ascii_isprint(*p) && conv_len < from_len) {
4880 conv_len += g_utf8_skip[*(guchar *)p];
4881 p += g_utf8_skip[*(guchar *)p];
4882 }
4883
4884 from_len -= conv_len;
4885 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
4886 &conv_len,
4887 &norm_utf7_p, &norm_utf7_len) == -1) {
4888 g_warning("iconv cannot convert %s to UTF-7\n",
4889 CS_INTERNAL);
4890 g_free(norm_utf7);
4891 return g_strdup(from);
4892 }
4893
4894 /* second iconv() call for flushing */
4895 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
4896 }
4897 }
4898
4899 *norm_utf7_p = '\0';
4900 to_str = g_string_new(NULL);
4901 for (p = norm_utf7; p < norm_utf7_p; p++) {
4902 /* replace: '&' -> "&-",
4903 '+' -> '&',
4904 "+-" -> '+',
4905 BASE64 '/' -> ',' */
4906 if (!in_escape && *p == '&') {
4907 g_string_append(to_str, "&-");
4908 } else if (!in_escape && *p == '+') {
4909 if (*(p + 1) == '-') {
4910 g_string_append_c(to_str, '+');
4911 p++;
4912 } else {
4913 g_string_append_c(to_str, '&');
4914 in_escape = TRUE;
4915 }
4916 } else if (in_escape && *p == '/') {
4917 g_string_append_c(to_str, ',');
4918 } else if (in_escape && *p == '-') {
4919 g_string_append_c(to_str, '-');
4920 in_escape = FALSE;
4921 } else {
4922 g_string_append_c(to_str, *p);
4923 }
4924 }
4925
4926 if (in_escape) {
4927 in_escape = FALSE;
4928 g_string_append_c(to_str, '-');
4929 }
4930
4931 to = g_string_free(to_str, FALSE);
4932 g_free(norm_utf7);
4933
4934 return to;
4935}
4936
4937static GSList *imap_get_seq_set_from_msglist(GSList *msglist, gint limit)
4938{
4939 GString *str;
4940 GSList *sorted_list, *cur;
4941 guint first, last, next;
4942 gchar *ret_str;
4943 GSList *ret_list = NULL;
4944 gint count = 0;
4945
4946 if (msglist == NULL)
4947 return NULL;
4948
4949 str = g_string_sized_new(256);
4950
4951 sorted_list = g_slist_copy(msglist);
4952 sorted_list = procmsg_sort_msg_list(sorted_list, SORT_BY_NUMBER,
4953 SORT_ASCENDING);
4954
4955 first = ((MsgInfo *)sorted_list->data)->msgnum;
4956
4957 for (cur = sorted_list; cur != NULL; cur = cur->next) {
4958 ++count;
4959 last = ((MsgInfo *)cur->data)->msgnum;
4960 if (cur->next)
4961 next = ((MsgInfo *)cur->next->data)->msgnum;
4962 else
4963 next = 0;
4964
4965 if (limit > 0 && count >= limit) {
4966 if (str->len > 0)
4967 g_string_append_c(str, ',');
4968 if (first == last)
4969 g_string_sprintfa(str, "%u", first);
4970 else
4971 g_string_sprintfa(str, "%u:%u", first, last);
4972
4973 first = next;
4974
4975 ret_str = g_strdup(str->str);
4976 ret_list = g_slist_append(ret_list, ret_str);
4977 g_string_truncate(str, 0);
4978 count = 0;
4979 continue;
4980 }
4981
4982 if (last + 1 != next || next == 0) {
4983 if (str->len > 0)
4984 g_string_append_c(str, ',');
4985 if (first == last)
4986 g_string_sprintfa(str, "%u", first);
4987 else
4988 g_string_sprintfa(str, "%u:%u", first, last);
4989
4990 first = next;
4991
4992 if (str->len > IMAP_CMD_LIMIT) {
4993 ret_str = g_strdup(str->str);
4994 ret_list = g_slist_append(ret_list, ret_str);
4995 g_string_truncate(str, 0);
4996 }
4997 }
4998 }
4999
5000 if (str->len > 0) {
5001 ret_str = g_strdup(str->str);
5002 ret_list = g_slist_append(ret_list, ret_str);
5003 }
5004
5005 g_slist_free(sorted_list);
5006 g_string_free(str, TRUE);
5007
5008 return ret_list;
5009}
5010
5011static gint imap_seq_set_get_count(const gchar *seq_set)
5012{
5013 gint count = 0;
5014 guint first, last;
5015 gchar *tmp, *p, *q;
5016
5017 p = q = tmp = g_strdup(seq_set);
5018
5019 while (*p) {
5020 if (*p == ',') {
5021 *p = '\0';
5022 if (sscanf(q, "%u:%u", &first, &last) == 2)
5023 count += last - first + 1;
5024 else if (sscanf(q, "%u", &first) == 1)
5025 count++;
5026 q = ++p;
5027 } else
5028 ++p;
5029 }
5030 if (q != p) {
5031 if (sscanf(q, "%u:%u", &first, &last) == 2)
5032 count += last - first + 1;
5033 else if (sscanf(q, "%u", &first) == 1)
5034 count++;
5035 }
5036
5037 g_free(tmp);
5038
5039 return count;
5040}
5041
5042static void imap_seq_set_free(GSList *seq_list)
5043{
5044 slist_free_strings(seq_list);
5045 g_slist_free(seq_list);
5046}
5047
5048static GHashTable *imap_get_uid_table(GArray *array)
5049{
5050 GHashTable *table;
5051 gint i;
5052 guint32 uid;
5053
5054 g_return_val_if_fail(array != NULL, NULL);
5055
5056 table = g_hash_table_new(NULL, g_direct_equal);
5057
5058 for (i = 0; i < array->len; i++) {
5059 uid = g_array_index(array, guint32, i);
5060 g_hash_table_insert(table, GUINT_TO_POINTER(uid),
5061 GINT_TO_POINTER(i + 1));
5062 }
5063
5064 return table;
5065}
5066
5067static gboolean imap_rename_folder_func(GNode *node, gpointer data)
5068{
5069 FolderItem *item = node->data;
5070 gchar **paths = data;
5071 const gchar *oldpath = paths[0];
5072 const gchar *newpath = paths[1];
5073 gchar *base;
5074 gchar *new_itempath;
5075 gint oldpathlen;
5076
5077 oldpathlen = strlen(oldpath);
5078 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
5079 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
5080 return TRUE;
5081 }
5082
5083 base = item->path + oldpathlen;
5084 while (*base == '/') base++;
5085 if (*base == '\0')
5086 new_itempath = g_strdup(newpath);
5087 else
5088 new_itempath = g_strconcat(newpath, "/", base, NULL);
5089 g_free(item->path);
5090 item->path = new_itempath;
5091
5092 return FALSE;
5093}
5094
5095#if USE_THREADS
5096static void imap_thread_run_proxy(gpointer push_data, gpointer data)
5097{
5098 IMAPRealSession *real = (IMAPRealSession *)data;
5099
5100 debug_print("imap_thread_run_proxy (%p): calling thread_func\n", g_thread_self());
5101 real->retval = real->thread_func(IMAP_SESSION(real), real->thread_data);
5102 g_atomic_int_set(&real->flag, 1);
5103 g_main_context_wakeup(NULL);
5104 debug_print("imap_thread_run_proxy (%p): thread_func done\n", g_thread_self());
5105}
5106
5107static gint imap_thread_run(IMAPSession *session, IMAPThreadFunc func,
5108 gpointer data)
5109{
5110 IMAPRealSession *real = (IMAPRealSession *)session;
5111 gint ret;
5112
5113 if (real->is_running) {
5114 g_warning("imap_thread_run: thread is already running");
5115 return IMAP_ERROR;
5116 }
5117
5118 if (!real->pool) {
5119 real->pool = g_thread_pool_new(imap_thread_run_proxy, real,
5120 -1, FALSE, NULL);
5121 if (!real->pool)
5122 return IMAP_ERROR;
5123 }
5124
5125 real->is_running = TRUE;
5126 real->thread_func = func;
5127 real->thread_data = data;
5128 real->flag = 0;
5129 real->retval = 0;
5130
5131 g_thread_pool_push(real->pool, real, NULL);
5132
5133 while (g_atomic_int_get(&real->flag) == 0)
5134 event_loop_iterate();
5135
5136 real->is_running = FALSE;
5137 real->thread_func = NULL;
5138 real->thread_data = NULL;
5139 real->flag = 0;
5140 ret = real->retval;
5141 real->retval = 0;
5142 log_flush();
5143
5144 return ret;
5145}
5146
5147static gint imap_thread_run_progress(IMAPSession *session, IMAPThreadFunc func,
5148 IMAPProgressFunc progress_func,
5149 gpointer data)
5150{
5151 IMAPRealSession *real = (IMAPRealSession *)session;
5152 gint prev_count = 0;
5153 gint ret;
5154
5155 if (real->is_running) {
5156 g_warning("imap_thread_run: thread is already running");
5157 return IMAP_ERROR;
5158 }
5159
5160 if (!real->pool) {
5161 real->pool = g_thread_pool_new(imap_thread_run_proxy, real,
5162 -1, FALSE, NULL);
5163 if (!real->pool)
5164 return IMAP_ERROR;
5165 }
5166
5167 real->is_running = TRUE;
5168 real->thread_func = func;
5169 real->thread_data = data;
5170 real->flag = 0;
5171 real->retval = 0;
5172 real->prog_count = 0;
5173 real->prog_total = 0;
5174
5175 g_thread_pool_push(real->pool, real, NULL);
5176
5177 while (g_atomic_int_get(&real->flag) == 0) {
5178 event_loop_iterate();
5179 if (prev_count != real->prog_count && real->prog_total > 0) {
5180 progress_func(session, real->prog_count,
5181 real->prog_total, data);
5182 prev_count = real->prog_count;
5183 }
5184 }
5185
5186 real->is_running = FALSE;
5187 real->thread_func = NULL;
5188 real->thread_data = NULL;
5189 real->flag = 0;
5190 ret = real->retval;
5191 real->retval = 0;
5192 real->prog_count = 0;
5193 real->prog_total = 0;
5194 log_flush();
5195
5196 return ret;
5197}
5198#endif /* USE_THREADS */
5199
5200gboolean imap_is_session_active(IMAPFolder *folder)
5201{
5202#if USE_THREADS
5203 IMAPRealSession *real;
5204
5205 g_return_val_if_fail(folder != NULL, FALSE);
5206
5207 real = (IMAPRealSession *)(REMOTE_FOLDER(folder)->session);
5208 if (!real)
5209 return FALSE;
5210
5211 return real->is_running;
5212#else
5213 return FALSE;
5214#endif
5215}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2011 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __IMAP_H__
21#define __IMAP_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28#include <time.h>
29
30#include "folder.h"
31#include "session.h"
32#include "procmsg.h"
33
34typedef struct _IMAPFolder IMAPFolder;
35typedef struct _IMAPSession IMAPSession;
36typedef struct _IMAPNameSpace IMAPNameSpace;
37
38#define IMAP_FOLDER(obj) ((IMAPFolder *)obj)
39#define IMAP_SESSION(obj) ((IMAPSession *)obj)
40
41#include "prefs_account.h"
42
43typedef enum
44{
45 IMAP_AUTH_LOGIN = 1 << 0,
46 IMAP_AUTH_CRAM_MD5 = 1 << 1,
47 IMAP_AUTH_PLAIN = 1 << 2
48} IMAPAuthType;
49
50struct _IMAPFolder
51{
52 RemoteFolder rfolder;
53
54 /* list of IMAPNameSpace */
55 GList *ns_personal;
56 GList *ns_others;
57 GList *ns_shared;
58};
59
60struct _IMAPSession
61{
62 Session session;
63
64 gboolean authenticated;
65
66 gchar **capability;
67 gboolean uidplus;
68
69 gchar *mbox;
70 guint cmd_count;
71};
72
73struct _IMAPNameSpace
74{
75 gchar *name;
76 gchar separator;
77};
78
79#define IMAP_SUCCESS 0
80#define IMAP_SOCKET 2
81#define IMAP_AUTHFAIL 3
82#define IMAP_PROTOCOL 4
83#define IMAP_SYNTAX 5
84#define IMAP_IOERR 6
85#define IMAP_ERROR 7
86#define IMAP_EAGAIN 8
87
88#define IMAPBUFSIZE 8192
89
90typedef enum
91{
92 IMAP_FLAG_SEEN = 1 << 0,
93 IMAP_FLAG_ANSWERED = 1 << 1,
94 IMAP_FLAG_FLAGGED = 1 << 2,
95 IMAP_FLAG_DELETED = 1 << 3,
96 IMAP_FLAG_DRAFT = 1 << 4,
97
98 /* color label keywords : 1 << 7 ... 1 << 9
99 compatible with procmsg.h: MSG_CLABEL* macros */
100} IMAPFlags;
101
102#define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
103#define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
104#define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
105#define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
106#define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
107
108#define IMAP_GET_COLORLABEL(flags) (flags & (7 << MSG_CLABEL_SBIT))
109#define IMAP_GET_COLORLABEL_VALUE(flags) \
110 (IMAP_GET_COLORLABEL(flags) >> MSG_CLABEL_SBIT)
111#define IMAP_SET_COLORLABEL_VALUE(flags, v) \
112 ((flags) |= ((v & 7) << MSG_CLABEL_SBIT))
113
114FolderClass *imap_get_class (void);
115
116gint imap_msg_set_perm_flags (MsgInfo *msginfo,
117 MsgPermFlags flags);
118gint imap_msg_unset_perm_flags (MsgInfo *msginfo,
119 MsgPermFlags flags);
120gint imap_msg_list_set_perm_flags (GSList *msglist,
121 MsgPermFlags flags);
122gint imap_msg_list_unset_perm_flags (GSList *msglist,
123 MsgPermFlags flags);
124
125gint imap_msg_list_set_colorlabel_flags (GSList *msglist,
126 guint color);
127
128gboolean imap_is_session_active (IMAPFolder *folder);
129
130#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 @@
1EXPORTS
2 account_address_exist @ 1
3 account_append @ 2
4 account_destroy @ 3
5 account_find_from_address @ 4
6 account_find_from_id @ 5
7 account_find_from_item @ 6
8 account_find_from_item_property @ 7
9 account_find_from_message_file @ 8
10 account_find_from_msginfo @ 9
11 account_find_from_smtp_server @ 10
12 account_foreach @ 11
13 account_get_current_account @ 12
14 account_get_default @ 13
15 account_get_list @ 14
16 account_get_special_folder @ 15
17 account_list_free @ 16
18 account_read_config_all @ 17
19 account_set_as_default @ 18
20 account_update_lock @ 19
21 account_update_unlock @ 20
22 account_updated @ 21
23 account_write_config_all @ 22
24 add_history @ 23
25 address_equal @ 24
26 address_list_append @ 25
27 address_list_append_orig @ 26
28 address_table @ 27 DATA
29 base64_decode @ 28
30 base64_decoder_decode @ 29
31 base64_decoder_free @ 30
32 base64_decoder_new @ 31
33 base64_encode @ 32
34 canonicalize_file @ 33
35 canonicalize_file_replace @ 34
36 canonicalize_file_stream @ 35
37 canonicalize_str @ 36
38 change_dir @ 37
39 change_file_mode_rw @ 38
40 check_line_length @ 39
41 close_log_file @ 40
42 conv_check_file_encoding @ 41
43 conv_code_converter_destroy @ 42
44 conv_code_converter_new @ 43
45 conv_codeset_strdup_full @ 44
46 conv_convert @ 45
47 conv_copy_dir @ 46
48 conv_copy_file @ 47
49 conv_encode_filename @ 48
50 conv_encode_header @ 49
51 conv_filename_from_utf8 @ 50
52 conv_filename_to_utf8 @ 51
53 conv_get_autodetect_type @ 52
54 conv_get_charset_from_str @ 53
55 conv_get_charset_str @ 54
56 conv_get_code_conv_func @ 55
57 conv_get_current_locale @ 56
58 conv_get_internal_charset @ 57
59 conv_get_internal_charset_str @ 58
60 conv_get_locale_charset @ 59
61 conv_get_locale_charset_str @ 60
62 conv_get_outgoing_charset @ 61
63 conv_get_outgoing_charset_str @ 62
64 conv_guess_ja_encoding @ 63
65 conv_iconv_strdup @ 64
66 conv_iconv_strdup_with_cd @ 65
67 conv_is_ja_locale @ 66
68 conv_is_multibyte_encoding @ 67
69 conv_localetodisp @ 68
70 conv_mb_alnum @ 69
71 conv_set_autodetect_type @ 70
72 conv_unmime_header @ 71
73 conv_utf8todisp @ 72
74 copy_dir @ 73
75 copy_file @ 74
76 copy_file_part @ 75
77 copy_mbox @ 76
78 cur_account @ 77 DATA
79 custom_header_find @ 78
80 custom_header_free @ 79
81 custom_header_get_str @ 80
82 custom_header_read_config @ 81
83 custom_header_read_str @ 82
84 custom_header_write_config @ 83
85 debug_print @ 84
86 decode_uri @ 85
87 decode_xdigit_encoded_str @ 86
88 dirent_is_directory @ 87
89 dirent_is_regular_file @ 88
90 display_header_prop_free @ 89
91 display_header_prop_get_str @ 90
92 display_header_prop_read_str @ 91
93 eliminate_address_comment @ 92
94 eliminate_parenthesis @ 93
95 eliminate_quote @ 94
96 empty_mbox @ 95
97 encode_uri @ 96
98 event_loop_iterate @ 97
99 execute_async @ 98
100 execute_command_line @ 99
101 execute_command_line_async_wait @ 100
102 execute_open_file @ 101
103 execute_print_file @ 102
104 execute_sync @ 103
105 export_to_mbox @ 104
106 extract_address @ 105
107 extract_list_id_str @ 106
108 extract_parenthesis @ 107
109 extract_parenthesis_with_escape @ 108
110 extract_parenthesis_with_skip_quote @ 109
111 extract_quote @ 110
112 extract_quote_with_escape @ 111
113 fd_accept @ 112
114 fd_close @ 113
115 fd_connect_inet @ 114
116 fd_connect_unix @ 115
117 fd_getline @ 116
118 fd_gets @ 117
119 fd_open_inet @ 118
120 fd_open_unix @ 119
121 fd_read @ 120
122 fd_recv @ 121
123 fd_write @ 122
124 fd_write_all @ 123
125 file_exist @ 124
126 file_read_stream_to_str @ 125
127 file_read_to_str @ 126
128 filter_action_exec @ 127
129 filter_action_list_free @ 128
130 filter_action_new @ 129
131 filter_apply @ 130
132 filter_apply_msginfo @ 131
133 filter_cond_list_free @ 132
134 filter_cond_new @ 133
135 filter_get_keyword_from_msg @ 134
136 filter_get_str @ 135
137 filter_info_free @ 136
138 filter_info_new @ 137
139 filter_list_delete_path @ 138
140 filter_list_rename_path @ 139
141 filter_match_rule @ 140
142 filter_read_config @ 141
143 filter_read_file @ 142
144 filter_read_str @ 143
145 filter_rule_delete_action_by_dest_path @ 144
146 filter_rule_free @ 145
147 filter_rule_list_free @ 146
148 filter_rule_match_type_str_to_enum @ 147
149 filter_rule_new @ 148
150 filter_rule_rename_dest_path @ 149
151 filter_rule_requires_full_headers @ 150
152 filter_write_config @ 151
153 filter_write_file @ 152
154 filter_xml_node_to_filter_list @ 153
155 folder_add @ 154
156 folder_create_tree @ 155
157 folder_destroy @ 156
158 folder_find_child_item_by_name @ 157
159 folder_find_from_name @ 158
160 folder_find_from_path @ 159
161 folder_find_item_and_num_from_id @ 160
162 folder_find_item_from_identifier @ 161
163 folder_find_item_from_path @ 162
164 folder_get_default_draft @ 163
165 folder_get_default_folder @ 164
166 folder_get_default_inbox @ 165
167 folder_get_default_outbox @ 166
168 folder_get_default_queue @ 167
169 folder_get_default_trash @ 168
170 folder_get_identifier @ 169
171 folder_get_list @ 170
172 folder_get_path @ 171
173 folder_get_status @ 172
174 folder_item_add_msg @ 173
175 folder_item_add_msg_msginfo @ 174
176 folder_item_add_msgs @ 175
177 folder_item_add_msgs_msginfo @ 176
178 folder_item_append @ 177
179 folder_item_close @ 178
180 folder_item_compare @ 179
181 folder_item_copy @ 180
182 folder_item_copy_msg @ 181
183 folder_item_copy_msgs @ 182
184 folder_item_destroy @ 183
185 folder_item_fetch_all_msg @ 184
186 folder_item_fetch_msg @ 185
187 folder_item_get_cache_file @ 186
188 folder_item_get_identifier @ 187
189 folder_item_get_mark_file @ 188
190 folder_item_get_msg_list @ 189
191 folder_item_get_msginfo @ 190
192 folder_item_get_path @ 191
193 folder_item_get_uncached_msg_list @ 192
194 folder_item_is_msg_changed @ 193
195 folder_item_move_msg @ 194
196 folder_item_move_msgs @ 195
197 folder_item_new @ 196
198 folder_item_remove @ 197
199 folder_item_remove_all_msg @ 198
200 folder_item_remove_children @ 199
201 folder_item_remove_msg @ 200
202 folder_item_remove_msgs @ 201
203 folder_item_scan @ 202
204 folder_item_scan_foreach @ 203
205 folder_local_folder_destroy @ 204
206 folder_local_folder_init @ 205
207 folder_new @ 206
208 folder_read_list @ 207
209 folder_remote_folder_destroy @ 208
210 folder_remote_folder_init @ 209
211 folder_scan_tree @ 210
212 folder_set_missing_folders @ 211
213 folder_set_name @ 212
214 folder_set_ui_func @ 213
215 folder_tree_destroy @ 214
216 folder_unref_account_all @ 215
217 folder_write_list @ 216
218 fromuutobits @ 217
219 generate_mime_boundary @ 218
220 get_abbrev_newsgroup_name @ 219
221 get_alt_filename @ 220
222 get_command_output @ 221
223 get_debug_mode @ 222
224 get_document_dir @ 223
225 get_domain_name @ 224
226 get_file_size @ 225
227 get_file_size_as_crlf @ 226
228 get_home_dir @ 227
229 get_imap_cache_dir @ 228
230 get_left_file_size @ 229
231 get_mail_base_dir @ 230
232 get_mime_tmp_dir @ 231
233 get_news_cache_dir @ 232
234 get_next_word_len @ 233
235 get_old_rc_dir @ 234
236 get_outgoing_rfc2822_file @ 235
237 get_outgoing_rfc2822_str @ 236
238 get_quote_level @ 237
239 get_rc_dir @ 238
240 get_rfc822_date @ 239
241 get_startup_dir @ 240
242 get_template_dir @ 241
243 get_tmp_dir @ 242
244 get_tmp_file @ 243
245 get_uri_len @ 244
246 get_uri_path @ 245
247 hash_free_strings @ 246
248 hash_free_value_mem @ 247
249 html_parse @ 248
250 html_parser_destroy @ 249
251 html_parser_new @ 250
252 imap_get_class @ 251
253 imap_msg_list_set_perm_flags @ 252
254 imap_msg_list_unset_perm_flags @ 253
255 imap_msg_set_perm_flags @ 254
256 imap_msg_unset_perm_flags @ 255
257 input_query_password @ 256
258 is_ascii_str @ 257
259 is_dir_exist @ 258
260 is_file_entry_exist @ 259
261 is_header_line @ 260
262 is_next_nonascii @ 261
263 is_uri_string @ 262
264 itos @ 263
265 itos_buf @ 264
266 list_free_strings @ 265
267 lock_mbox @ 266
268 log_error @ 267
269 log_flush @ 268
270 log_message @ 269
271 log_print @ 270
272 log_warning @ 271
273 log_write @ 272
274 make_dir @ 273
275 make_dir_hier @ 274
276 md5_hex_hmac @ 275
277 md5_hmac @ 276
278 mh_get_class @ 277
279 move_file @ 278
280 my_gethostbyname @ 279
281 my_memmem @ 280
282 my_strftime @ 281
283 my_tmpfile @ 282
284 news_get_class @ 283
285 news_get_group_list @ 284
286 news_group_list_free @ 285
287 news_post @ 286
288 news_post_stream @ 287
289 news_remove_group_list_cache @ 288
290 newsgroup_list_append @ 289
291 nntp_article @ 290
292 nntp_body @ 291
293 nntp_get_article @ 292
294 nntp_group @ 293
295 nntp_head @ 294
296 nntp_list @ 295
297 nntp_mode @ 296
298 nntp_newgroups @ 297
299 nntp_newnews @ 298
300 nntp_next @ 299
301 nntp_post @ 300
302 nntp_session_new @ 301
303 nntp_stat @ 302
304 nntp_xhdr @ 303
305 nntp_xover @ 304
306 normalize_address_field @ 305
307 normalize_newlines @ 306
308 open_uri @ 307
309 path_cmp @ 308
310 pop3_delete_recv @ 309
311 pop3_delete_send @ 310
312 pop3_gen_send @ 311
313 pop3_get_uidl_table @ 312
314 pop3_getauth_apop_send @ 313
315 pop3_getauth_pass_send @ 314
316 pop3_getauth_user_send @ 315
317 pop3_getrange_last_recv @ 316
318 pop3_getrange_last_send @ 317
319 pop3_getrange_stat_recv @ 318
320 pop3_getrange_stat_send @ 319
321 pop3_getrange_uidl_recv @ 320
322 pop3_getrange_uidl_send @ 321
323 pop3_getsize_list_recv @ 322
324 pop3_getsize_list_send @ 323
325 pop3_greeting_recv @ 324
326 pop3_logout_send @ 325
327 pop3_ok @ 326
328 pop3_retr_recv @ 327
329 pop3_retr_send @ 328
330 pop3_session_new @ 329
331 pop3_stls_recv @ 330
332 pop3_stls_send @ 331
333 pop3_write_msg_to_file @ 332
334 pop3_write_uidl_list @ 333
335 prefs_account_apply_tmp_prefs @ 334
336 prefs_account_free @ 335
337 prefs_account_get_params @ 336
338 prefs_account_get_tmp_prefs @ 337
339 prefs_account_new @ 338
340 prefs_account_read_config @ 339
341 prefs_account_set_tmp_prefs @ 340
342 prefs_account_write_config_all @ 341
343 prefs_common @ 342 DATA
344 prefs_common_get @ 343
345 prefs_common_get_params @ 344
346 prefs_common_junk_filter_list_set @ 345
347 prefs_common_junk_folder_rename_path @ 346
348 prefs_common_read_config @ 347
349 prefs_common_write_config @ 348
350 prefs_file_close @ 349
351 prefs_file_close_revert @ 350
352 prefs_file_get_backup_generation @ 351
353 prefs_file_open @ 352
354 prefs_file_set_backup_generation @ 353
355 prefs_file_write_param @ 354
356 prefs_free @ 355
357 prefs_param_table_destroy @ 356
358 prefs_param_table_get @ 357
359 prefs_read_config @ 358
360 prefs_set_default @ 359
361 prefs_write_config @ 360
362 proc_mbox @ 361
363 proc_mbox_full @ 362
364 procheader_add_header_list @ 363
365 procheader_copy_header_list @ 364
366 procheader_date_get_localtime @ 365
367 procheader_date_parse @ 366
368 procheader_find_header_list @ 367
369 procheader_get_fromname @ 368
370 procheader_get_header_array @ 369
371 procheader_get_header_array_asis @ 370
372 procheader_get_header_array_for_display @ 371
373 procheader_get_header_fields @ 372
374 procheader_get_header_list @ 373
375 procheader_get_header_list_from_file @ 374
376 procheader_get_header_list_from_msginfo @ 375
377 procheader_get_one_field @ 376
378 procheader_get_toname @ 377
379 procheader_get_unfolded_line @ 378
380 procheader_header_array_destroy @ 379
381 procheader_header_free @ 380
382 procheader_header_list_destroy @ 381
383 procheader_merge_header_list @ 382
384 procheader_merge_header_list_dup @ 383
385 procheader_parse_file @ 384
386 procheader_parse_str @ 385
387 procheader_parse_stream @ 386
388 procmime_decode_content @ 387
389 procmime_execute_open_file @ 388
390 procmime_find_string @ 389
391 procmime_find_string_part @ 390
392 procmime_get_all_parts @ 391
393 procmime_get_encoding_for_charset @ 392
394 procmime_get_encoding_for_str @ 393
395 procmime_get_encoding_for_text_file @ 394
396 procmime_get_encoding_str @ 395
397 procmime_get_first_text_content @ 396
398 procmime_get_mime_type @ 397
399 procmime_get_part @ 398
400 procmime_get_part_file_name @ 399
401 procmime_get_part_fp @ 400
402 procmime_get_text_content @ 401
403 procmime_get_tmp_file_name @ 402
404 procmime_mimeinfo_free_all @ 403
405 procmime_mimeinfo_insert @ 404
406 procmime_mimeinfo_new @ 405
407 procmime_mimeinfo_next @ 406
408 procmime_scan_content_disposition @ 407
409 procmime_scan_content_type @ 408
410 procmime_scan_content_type_str @ 409
411 procmime_scan_encoding @ 410
412 procmime_scan_message @ 411
413 procmime_scan_mime_header @ 412
414 procmime_scan_mime_type @ 413
415 procmime_scan_multipart_message @ 414
416 procmsg_add_cache_queue @ 415
417 procmsg_add_flags @ 416
418 procmsg_add_mark_queue @ 417
419 procmsg_clear_cache @ 418
420 procmsg_clear_mark @ 419
421 procmsg_cmp_msgnum_for_sort @ 420
422 procmsg_copy_messages @ 421
423 procmsg_empty_all_trash @ 422
424 procmsg_empty_trash @ 423
425 procmsg_flush_cache_queue @ 424
426 procmsg_flush_folder @ 425
427 procmsg_flush_folder_foreach @ 426
428 procmsg_flush_mark_queue @ 427
429 procmsg_get_last_num_in_msg_list @ 428
430 procmsg_get_mark_sum @ 429
431 procmsg_get_message_file @ 430
432 procmsg_get_message_file_list @ 431
433 procmsg_get_message_file_path @ 432
434 procmsg_get_msginfo @ 433
435 procmsg_get_thread_date @ 434
436 procmsg_get_thread_tree @ 435
437 procmsg_mark_all_read @ 436
438 procmsg_message_file_list_free @ 437
439 procmsg_move_messages @ 438
440 procmsg_msg_exist @ 439
441 procmsg_msg_hash_table_append @ 440
442 procmsg_msg_hash_table_create @ 441
443 procmsg_msg_list_free @ 442
444 procmsg_msginfo_copy @ 443
445 procmsg_msginfo_equal @ 444
446 procmsg_msginfo_free @ 445
447 procmsg_msginfo_get_full_info @ 446
448 procmsg_open_cache_file @ 447
449 procmsg_open_data_file @ 448
450 procmsg_open_mark_file @ 449
451 procmsg_open_message @ 450
452 procmsg_open_message_decrypted @ 451
453 procmsg_print_message @ 452
454 procmsg_print_message_part @ 453
455 procmsg_read_cache @ 454
456 procmsg_read_cache_data_str @ 455
457 procmsg_remove_all_cached_messages @ 456
458 procmsg_save_to_outbox @ 457
459 procmsg_set_auto_decrypt_message @ 458
460 procmsg_set_decrypt_message_func @ 459
461 procmsg_set_flags @ 460
462 procmsg_sort_msg_list @ 461
463 procmsg_to_folder_hash_table_create @ 462
464 procmsg_trash_messages_exist @ 463
465 procmsg_write_cache @ 464
466 procmsg_write_cache_list @ 465
467 procmsg_write_flags @ 466
468 procmsg_write_flags_for_multiple_folders @ 467
469 procmsg_write_flags_list @ 468
470 progress_show @ 469
471 ptr_array_free_strings @ 470
472 qp_decode_line @ 471
473 qp_decode_q_encoding @ 472
474 qp_encode_line @ 473
475 qp_get_q_encoding_len @ 474
476 qp_q_encode @ 475
477 recv_bytes @ 476
478 recv_bytes_write @ 477
479 recv_bytes_write_to_file @ 478
480 recv_set_ui_func @ 479
481 recv_write @ 480
482 recv_write_to_file @ 481
483 references_list_append @ 482
484 references_list_prepend @ 483
485 remote_tzoffset_sec @ 484
486 remove_all_files @ 485
487 remove_all_numbered_files @ 486
488 remove_dir_recursive @ 487
489 remove_expired_files @ 488
490 remove_numbered_files @ 489
491 remove_return @ 490
492 remove_space @ 491
493 rename_force @ 492
494 s_gnet_md5_clone @ 493
495 s_gnet_md5_copy_string @ 494
496 s_gnet_md5_delete @ 495
497 s_gnet_md5_equal @ 496
498 s_gnet_md5_final @ 497
499 s_gnet_md5_get_digest @ 498
500 s_gnet_md5_get_string @ 499
501 s_gnet_md5_hash @ 500
502 s_gnet_md5_new @ 501
503 s_gnet_md5_new_incremental @ 502
504 s_gnet_md5_new_string @ 503
505 s_gnet_md5_update @ 504
506 scan_mailto_url @ 505
507 session_connect @ 506
508 session_destroy @ 507
509 session_disconnect @ 508
510 session_init @ 509
511 session_is_connected @ 510
512 session_recv_data @ 511
513 session_recv_data_as_file @ 512
514 session_recv_msg @ 513
515 session_send_data @ 514
516 session_send_msg @ 515
517 session_set_access_time @ 516
518 session_set_recv_data_notify @ 517
519 session_set_recv_data_progressive_notify @ 518
520 session_set_recv_message_notify @ 519
521 session_set_send_data_notify @ 520
522 session_set_send_data_progressive_notify @ 521
523 session_set_timeout @ 522
524 session_start_tls @ 523
525 set_debug_mode @ 524
526 set_event_loop_func @ 525
527 set_input_query_password_func @ 526
528 set_log_file @ 527
529 set_log_show_status_func @ 528
530 set_log_ui_func @ 529
531 set_log_ui_func_full @ 530
532 set_log_verbosity @ 531
533 set_progress_func @ 532
534 set_rc_dir @ 533
535 set_startup_dir @ 534
536 set_ui_update_func @ 535
537 sinfo_equal @ 536
538 sinfo_hash @ 537
539 slist_free_strings @ 538
540 smtp_session_new @ 539
541 sock_add_watch @ 540
542 sock_add_watch_poll @ 541
543 sock_cleanup @ 542
544 sock_close @ 543
545 sock_connect @ 544
546 sock_connect_async_thread @ 545
547 sock_connect_async_thread_wait @ 546
548 sock_getline @ 547
549 sock_gets @ 548
550 sock_has_read_data @ 549
551 sock_init @ 550
552 sock_is_nonblocking_mode @ 551
553 sock_peek @ 552
554 sock_printf @ 553
555 sock_puts @ 554
556 sock_read @ 555
557 sock_set_io_timeout @ 556
558 sock_set_nonblocking_mode @ 557
559 sock_watch_funcs @ 558 DATA
560 sock_write @ 559
561 sock_write_all @ 560
562 ssl_done @ 561
563 ssl_done_socket @ 562
564 ssl_getline @ 563
565 ssl_gets @ 564
566 ssl_init @ 565
567 ssl_init_socket @ 566
568 ssl_init_socket_with_method @ 567
569 ssl_peek @ 568
570 ssl_read @ 569
571 ssl_set_verify_func @ 570
572 ssl_write @ 571
573 ssl_write_all @ 572
574 status_print @ 573
575 str_case_equal @ 574
576 str_case_find @ 575
577 str_case_find_equal @ 576
578 str_case_hash @ 577
579 str_find @ 578
580 str_find_equal @ 579
581 str_find_format_times @ 580
582 str_has_suffix_case @ 581
583 str_open_as_stream @ 582
584 str_write_to_file @ 583
585 strcasestr @ 584
586 strchomp_all @ 585
587 strchr_parenthesis_close @ 586
588 strchr_with_skip_quote @ 587
589 strcmp2 @ 588
590 strcrchomp @ 589
591 string_table_free @ 590
592 string_table_free_string @ 591
593 string_table_get_stats @ 592
594 string_table_insert_string @ 593
595 string_table_lookup_string @ 594
596 string_table_new @ 595
597 strncpy2 @ 596
598 strrchr_with_skip_quote @ 597
599 strretchomp @ 598
600 strsplit_csv @ 599
601 strsplit_parenthesis @ 600
602 strsplit_with_quote @ 601
603 strstr_with_skip_quote @ 602
604 strtailchomp @ 603
605 subject_compare @ 604
606 subject_compare_for_sort @ 605
607 subst_char @ 606
608 subst_chars @ 607
609 subst_control @ 608
610 subst_for_filename @ 609
611 subst_null @ 610
612 syl_app_create @ 611
613 syl_app_get @ 612
614 syl_app_get_type @ 613
615 syl_cleanup @ 614
616 syl_init @ 615
617 syl_init_gettext @ 616
618 syl_link @ 617
619 syl_marshal_VOID__POINTER_STRING_STRING @ 618
620 syl_marshal_VOID__POINTER_STRING_UINT @ 619
621 syl_save_all_state @ 620
622 syl_setup_rc_dir @ 621
623 to_human_readable @ 622
624 to_number @ 623
625 touufrombits @ 624
626 trim_string @ 625
627 trim_string_before @ 626
628 trim_subject @ 627
629 trim_subject_for_compare @ 628
630 trim_subject_for_sort @ 629
631 tzoffset @ 630
632 tzoffset_buf @ 631
633 tzoffset_sec @ 632
634 ui_update @ 633
635 uncanonicalize_file @ 634
636 uncanonicalize_file_replace @ 635
637 unfold_line @ 636
638 unlock_mbox @ 637
639 unmime_header @ 638
640 uri_list_extract_filenames @ 639
641 uriencode_for_filename @ 640
642 uriencode_for_mailto @ 641
643 utos_buf @ 642
644 uudigit @ 643 DATA
645 virtual_get_class @ 644
646 xml_attr_new @ 645
647 xml_close_file @ 646
648 xml_compare_tag @ 647
649 xml_copy_attr @ 648
650 xml_copy_tag @ 649
651 xml_file_put_escape_str @ 650
652 xml_file_put_node @ 651
653 xml_file_put_xml_decl @ 652
654 xml_free_node @ 653
655 xml_free_tree @ 654
656 xml_get_current_tag @ 655
657 xml_get_current_tag_attr @ 656
658 xml_get_dtd @ 657
659 xml_get_element @ 658
660 xml_node_new @ 659
661 xml_open_file @ 660
662 xml_parse_file @ 661
663 xml_parse_next_tag @ 662
664 xml_pop_tag @ 663
665 xml_push_tag @ 664
666 xml_read_line @ 665
667 xml_tag_add_attr @ 666
668 xml_tag_new @ 667
669 xml_truncate_buf @ 668
670 xml_unescape_str @ 669
671 strcasestr_with_skip_quote @ 670
672 is_path_parent @ 671
673 extract_addresses @ 672
674 to_unumber @ 673
675 imap_msg_list_set_colorlabel_flags @ 674
676 filter_get_addressbook_func @ 675
677 filter_set_addressbook_func @ 676
678 procmsg_flaginfo_list_free @ 677
679 procmsg_concat_partial_messages @ 678
680 get_last_empty_line_size @ 679
681 append_file_part @ 680
682 procmime_scan_content_type_partial @ 681
683 folder_get_default_junk @ 682
684 folder_get_junk @ 683
685 folder_set_junk @ 684
686 procmime_get_part_fp_fp @ 685
687 xml_escape_str @ 686
688 session_connect_full @ 687
689 socks_info_new @ 688
690 socks_info_free @ 689
691 socks_connect @ 690
692 socks4_connect @ 691
693 socks5_connect @ 692
694 folder_remote_folder_destroy_all_sessions @ 693
695 filter_junk_rule_create @ 694
696 folder_remote_folder_active_session_exist @ 695
697 procmsg_add_messages_from_queue @ 696
698 to_human_readable_buf @ 697
699 folder_item_is_trash @ 698
700 play_sound @ 699
701 session_get_error @ 700
702 sock_new @ 701
703 sock_info_connect @ 702
704 sock_info_connect_async_thread @ 703
705 sock_info_connect_async_thread_wait @ 704
706 folder_set_ui_func2 @ 705
707 folder_get_ui_func2 @ 706
708 folder_call_ui_func2 @ 707
709 export_msgs_to_mbox @ 708
710 copy_file_stream @ 709
711 procmsg_save_message_as_text @ 710
712 procmime_get_tmp_file_name_for_user @ 711
713 procmime_scan_message_stream @ 712
714 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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <stdio.h>
29#include <unistd.h>
30#include <string.h>
31#include <fcntl.h>
32#include <sys/file.h>
33#include <ctype.h>
34#include <time.h>
35
36#ifdef HAVE_LOCKFILE_H
37# include <lockfile.h>
38#endif
39
40#include "mbox.h"
41#include "procmsg.h"
42#include "procheader.h"
43#include "folder.h"
44#include "filter.h"
45#include "prefs_common.h"
46#include "prefs_account.h"
47#include "account.h"
48#include "utils.h"
49
50#define FPUTS_TO_TMP_ABORT_IF_FAIL(s) \
51{ \
52 if (fputs(s, tmp_fp) == EOF) { \
53 g_warning(_("can't write to temporary file\n")); \
54 fclose(tmp_fp); \
55 fclose(mbox_fp); \
56 g_unlink(tmp_file); \
57 g_free(tmp_file); \
58 return -1; \
59 } \
60}
61
62gint proc_mbox(FolderItem *dest, const gchar *mbox, GHashTable *folder_table)
63{
64 return proc_mbox_full(dest, mbox, folder_table,
65 folder_table ? TRUE : FALSE,
66 folder_table && prefs_common.enable_junk &&
67 prefs_common.filter_junk_on_recv ? TRUE : FALSE);
68}
69
70gint proc_mbox_full(FolderItem *dest, const gchar *mbox,
71 GHashTable *folder_table, gboolean apply_filter,
72 gboolean filter_junk)
73{
74 FILE *mbox_fp;
75 gchar buf[BUFFSIZE], from_line[BUFFSIZE];
76 gchar *tmp_file;
77 gint new_msgs = 0;
78 guint count = 0;
79 Folder *folder;
80 FilterRule *junk_rule = NULL;
81 GSList junk_fltlist = {NULL, NULL};
82 FolderItem *junk;
83
84 g_return_val_if_fail(dest != NULL, -1);
85 g_return_val_if_fail(dest->folder != NULL, -1);
86 g_return_val_if_fail(mbox != NULL, -1);
87
88 debug_print(_("Getting messages from %s into %s...\n"), mbox, dest->path);
89
90 folder = dest->folder;
91
92 if ((mbox_fp = g_fopen(mbox, "rb")) == NULL) {
93 FILE_OP_ERROR(mbox, "fopen");
94 return -1;
95 }
96
97 /* ignore empty lines on the head */
98 do {
99 if (fgets(buf, sizeof(buf), mbox_fp) == NULL) {
100 g_warning(_("can't read mbox file.\n"));
101 fclose(mbox_fp);
102 return -1;
103 }
104 } while (buf[0] == '\n' || buf[0] == '\r');
105
106 if (strncmp(buf, "From ", 5) != 0) {
107 g_warning(_("invalid mbox format: %s\n"), mbox);
108 fclose(mbox_fp);
109 return -1;
110 }
111
112 strcpy(from_line, buf);
113 if (fgets(buf, sizeof(buf), mbox_fp) == NULL) {
114 g_warning(_("malformed mbox: %s\n"), mbox);
115 fclose(mbox_fp);
116 return -1;
117 }
118
119 tmp_file = get_tmp_file();
120
121 if (filter_junk) {
122 junk = folder_get_junk(folder);
123 junk_rule = filter_junk_rule_create(NULL, junk, FALSE);
124 junk_fltlist.data = junk_rule;
125 }
126
127 do {
128 FILE *tmp_fp;
129 GSList *cur;
130 gchar *startp, *endp, *rpath;
131 gint empty_line;
132 gboolean is_next_msg = FALSE;
133 gboolean is_junk = FALSE;
134 FilterInfo *fltinfo;
135 MsgInfo *msginfo;
136
137 count++;
138 if (folder->ui_func)
139 folder->ui_func(folder, dest, folder->ui_func_data ? dest->folder->ui_func_data : GUINT_TO_POINTER(count));
140 if (folder_call_ui_func2(folder, dest, count, 0) == FALSE) {
141 debug_print("Import of mbox cancelled at %u\n", count);
142 break;
143 }
144
145 if ((tmp_fp = g_fopen(tmp_file, "wb")) == NULL) {
146 FILE_OP_ERROR(tmp_file, "fopen");
147 g_warning(_("can't open temporary file\n"));
148 filter_rule_free(junk_rule);
149 g_free(tmp_file);
150 fclose(mbox_fp);
151 return -1;
152 }
153 if (change_file_mode_rw(tmp_fp, tmp_file) < 0)
154 FILE_OP_ERROR(tmp_file, "chmod");
155
156 /* convert unix From into Return-Path */
157 startp = from_line + 5;
158 endp = strchr(startp, ' ');
159 if (endp == NULL)
160 rpath = g_strdup(startp);
161 else
162 rpath = g_strndup(startp, endp - startp);
163 g_strstrip(rpath);
164 g_snprintf(from_line, sizeof(from_line),
165 "Return-Path: %s\n", rpath);
166 g_free(rpath);
167
168 FPUTS_TO_TMP_ABORT_IF_FAIL(from_line);
169 FPUTS_TO_TMP_ABORT_IF_FAIL(buf);
170 from_line[0] = '\0';
171
172 empty_line = 0;
173
174 while (fgets(buf, sizeof(buf), mbox_fp) != NULL) {
175 if (buf[0] == '\n' || buf[0] == '\r') {
176 empty_line++;
177 buf[0] = '\0';
178 continue;
179 }
180
181 /* From separator */
182 while (!strncmp(buf, "From ", 5)) {
183 strcpy(from_line, buf);
184 if (fgets(buf, sizeof(buf), mbox_fp) == NULL) {
185 buf[0] = '\0';
186 break;
187 }
188
189 if (is_header_line(buf)) {
190 is_next_msg = TRUE;
191 break;
192 } else if (!strncmp(buf, "From ", 5)) {
193 continue;
194 } else if (!strncmp(buf, ">From ", 6)) {
195 g_memmove(buf, buf + 1, strlen(buf));
196 is_next_msg = TRUE;
197 break;
198 } else {
199 g_warning(_("unescaped From found:\n%s"),
200 from_line);
201 break;
202 }
203 }
204 if (is_next_msg) break;
205
206 if (empty_line > 0) {
207 while (empty_line--)
208 FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
209 empty_line = 0;
210 }
211
212 if (from_line[0] != '\0') {
213 FPUTS_TO_TMP_ABORT_IF_FAIL(from_line);
214 from_line[0] = '\0';
215 }
216
217 if (buf[0] != '\0') {
218 if (!strncmp(buf, ">From ", 6)) {
219 FPUTS_TO_TMP_ABORT_IF_FAIL(buf + 1);
220 } else
221 FPUTS_TO_TMP_ABORT_IF_FAIL(buf);
222
223 buf[0] = '\0';
224 }
225 }
226
227 if (empty_line > 0) {
228 while (--empty_line)
229 FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
230 }
231
232 if (fclose(tmp_fp) == EOF) {
233 FILE_OP_ERROR(tmp_file, "fclose");
234 g_warning(_("can't write to temporary file\n"));
235 filter_rule_free(junk_rule);
236 g_unlink(tmp_file);
237 g_free(tmp_file);
238 fclose(mbox_fp);
239 return -1;
240 }
241
242 fltinfo = filter_info_new();
243 fltinfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
244 fltinfo->flags.tmp_flags = MSG_RECEIVED;
245
246 msginfo = procheader_parse_file(tmp_file, fltinfo->flags,
247 FALSE);
248 if (!msginfo) {
249 g_warning("proc_mbox_full: procheader_parse_file failed");
250 filter_info_free(fltinfo);
251 filter_rule_free(junk_rule);
252 g_unlink(tmp_file);
253 g_free(tmp_file);
254 fclose(mbox_fp);
255 return -1;
256 }
257 fltinfo->flags = msginfo->flags;
258 msginfo->file_path = g_strdup(tmp_file);
259
260 if (filter_junk && prefs_common.enable_junk &&
261 prefs_common.filter_junk_before && junk_rule) {
262 filter_apply_msginfo(&junk_fltlist, msginfo, fltinfo);
263 if (fltinfo->drop_done)
264 is_junk = TRUE;
265 }
266
267 if (!fltinfo->drop_done && apply_filter)
268 filter_apply_msginfo(prefs_common.fltlist, msginfo,
269 fltinfo);
270
271 if (!fltinfo->drop_done &&
272 filter_junk && prefs_common.enable_junk &&
273 !prefs_common.filter_junk_before && junk_rule) {
274 filter_apply_msginfo(&junk_fltlist, msginfo, fltinfo);
275 if (fltinfo->drop_done)
276 is_junk = TRUE;
277 }
278
279 if (fltinfo->actions[FLT_ACTION_MOVE] == FALSE &&
280 fltinfo->actions[FLT_ACTION_DELETE] == FALSE) {
281 msginfo->flags = fltinfo->flags;
282 if (folder_item_add_msg_msginfo(dest, msginfo, FALSE) < 0) {
283 procmsg_msginfo_free(msginfo);
284 filter_info_free(fltinfo);
285 filter_rule_free(junk_rule);
286 g_unlink(tmp_file);
287 g_free(tmp_file);
288 fclose(mbox_fp);
289 return -1;
290 }
291 fltinfo->dest_list = g_slist_append(fltinfo->dest_list,
292 dest);
293 }
294
295 for (cur = fltinfo->dest_list; cur != NULL; cur = cur->next) {
296 FolderItem *drop_folder = (FolderItem *)cur->data;
297 gint val = 0;
298
299 if (folder_table) {
300 val = GPOINTER_TO_INT(g_hash_table_lookup
301 (folder_table,
302 drop_folder));
303 }
304 if (val == 0) {
305 if (folder_table) {
306 g_hash_table_insert(folder_table,
307 drop_folder,
308 GINT_TO_POINTER(1));
309 }
310 }
311 }
312
313 if (!is_junk &&
314 fltinfo->actions[FLT_ACTION_DELETE] == FALSE &&
315 fltinfo->actions[FLT_ACTION_MARK_READ] == FALSE)
316 new_msgs++;
317
318 procmsg_msginfo_free(msginfo);
319 filter_info_free(fltinfo);
320 g_unlink(tmp_file);
321 } while (from_line[0] != '\0');
322
323 if (junk_rule)
324 filter_rule_free(junk_rule);
325
326 g_free(tmp_file);
327 fclose(mbox_fp);
328 debug_print("%d new messages found.\n", new_msgs);
329
330 return new_msgs;
331}
332
333gint lock_mbox(const gchar *base, LockType type)
334{
335#ifdef G_OS_UNIX
336 gint retval = 0;
337
338 if (type == LOCK_FILE) {
339#if HAVE_LIBLOCKFILE
340 gchar *lockfile;
341
342 lockfile = g_strconcat(base, ".lock", NULL);
343 if (lockfile_create(lockfile, 0, L_PID) != L_SUCCESS) {
344 FILE_OP_ERROR(lockfile, "lockfile_create");
345 g_free(lockfile);
346 return -1;
347 }
348 g_free(lockfile);
349#else
350 gchar *lockfile, *locklink;
351 gint retry = 0;
352 FILE *lockfp;
353
354 lockfile = g_strdup_printf("%s.%d", base, getpid());
355 if ((lockfp = g_fopen(lockfile, "wb")) == NULL) {
356 FILE_OP_ERROR(lockfile, "fopen");
357 g_warning(_("can't create lock file %s\n"), lockfile);
358 g_warning(_("use 'flock' instead of 'file' if possible.\n"));
359 g_free(lockfile);
360 return -1;
361 }
362
363 fprintf(lockfp, "%d\n", getpid());
364 fclose(lockfp);
365
366 locklink = g_strconcat(base, ".lock", NULL);
367 while (link(lockfile, locklink) < 0) {
368 FILE_OP_ERROR(lockfile, "link");
369 if (retry >= 5) {
370 g_warning(_("can't create %s\n"), lockfile);
371 g_unlink(lockfile);
372 g_free(lockfile);
373 return -1;
374 }
375 if (retry == 0)
376 g_warning(_("mailbox is owned by another"
377 " process, waiting...\n"));
378 retry++;
379 sleep(5);
380 }
381 g_unlink(lockfile);
382 g_free(lockfile);
383#endif /* HAVE_LIBLOCKFILE */
384 } else if (type == LOCK_FLOCK) {
385 gint lockfd;
386
387 if ((lockfd = open(base, O_RDWR)) < 0) {
388 FILE_OP_ERROR(base, "open");
389 return -1;
390 }
391#if HAVE_LOCKF
392 if (lockf(lockfd, F_TLOCK, 0) < 0) {
393 perror("lockf");
394#elif HAVE_FLOCK
395 if (flock(lockfd, LOCK_EX|LOCK_NB) < 0) {
396 perror("flock");
397#else
398 {
399#endif /* HAVE_LOCKF */
400 g_warning(_("can't lock %s\n"), base);
401 if (close(lockfd) < 0)
402 perror("close");
403 return -1;
404 }
405 retval = lockfd;
406 } else {
407 g_warning(_("invalid lock type\n"));
408 return -1;
409 }
410
411 return retval;
412#else
413 return -1;
414#endif /* G_OS_UNIX */
415}
416
417gint unlock_mbox(const gchar *base, gint fd, LockType type)
418{
419 if (type == LOCK_FILE) {
420 gchar *lockfile;
421
422 lockfile = g_strconcat(base, ".lock", NULL);
423#if HAVE_LIBLOCKFILE
424 if (lockfile_remove(lockfile) != L_SUCCESS) {
425 FILE_OP_ERROR(lockfile, "lockfile_remove");
426#else
427 if (g_unlink(lockfile) < 0) {
428 FILE_OP_ERROR(lockfile, "unlink");
429#endif /* HAVE_LIBLOCKFILE */
430 g_free(lockfile);
431 return -1;
432 }
433 g_free(lockfile);
434
435 return 0;
436 } else if (type == LOCK_FLOCK) {
437#if HAVE_LOCKF
438 if (lockf(fd, F_ULOCK, 0) < 0) {
439 perror("lockf");
440#elif HAVE_FLOCK
441 if (flock(fd, LOCK_UN) < 0) {
442 perror("flock");
443#else
444 {
445#endif /* HAVE_LOCKF */
446 g_warning(_("can't unlock %s\n"), base);
447 if (close(fd) < 0)
448 perror("close");
449 return -1;
450 }
451
452 if (close(fd) < 0) {
453 perror("close");
454 return -1;
455 }
456
457 return 0;
458 }
459
460 g_warning(_("invalid lock type\n"));
461 return -1;
462}
463
464gint copy_mbox(const gchar *src, const gchar *dest)
465{
466 return copy_file(src, dest, TRUE);
467}
468
469void empty_mbox(const gchar *mbox)
470{
471#if HAVE_TRUNCATE
472 if (truncate(mbox, 0) < 0) {
473#endif
474 FILE *fp;
475
476#if HAVE_TRUNCATE
477 FILE_OP_ERROR(mbox, "truncate");
478#endif
479 if ((fp = g_fopen(mbox, "wb")) == NULL) {
480 FILE_OP_ERROR(mbox, "fopen");
481 g_warning(_("can't truncate mailbox to zero.\n"));
482 return;
483 }
484 fclose(fp);
485#if HAVE_TRUNCATE
486 }
487#endif
488}
489
490/* read all messages in SRC, and store them into one MBOX file. */
491gint export_to_mbox(FolderItem *src, const gchar *mbox)
492{
493 GSList *mlist;
494 gint ret = 0;
495
496 mlist = folder_item_get_msg_list(src, TRUE);
497 if (mlist) {
498 ret = export_msgs_to_mbox(src, mlist, mbox);
499 procmsg_msg_list_free(mlist);
500 }
501
502 return ret;
503}
504
505/* store MLIST into one MBOX file. */
506gint export_msgs_to_mbox(FolderItem *src, GSList *mlist, const gchar *mbox)
507{
508 GSList *cur;
509 MsgInfo *msginfo;
510 FILE *msg_fp;
511 FILE *mbox_fp;
512 gchar buf[BUFFSIZE];
513 PrefsAccount *cur_ac;
514 guint count = 0, length;
515 time_t date_t_;
516
517 g_return_val_if_fail(src != NULL, -1);
518 g_return_val_if_fail(src->folder != NULL, -1);
519 g_return_val_if_fail(mlist != NULL, -1);
520 g_return_val_if_fail(mbox != NULL, -1);
521
522 debug_print(_("Exporting messages from %s into %s...\n"),
523 src->path, mbox);
524
525 if ((mbox_fp = g_fopen(mbox, "wb")) == NULL) {
526 FILE_OP_ERROR(mbox, "fopen");
527 return -1;
528 }
529
530 cur_ac = account_get_current_account();
531
532 length = g_slist_length(mlist);
533
534 for (cur = mlist; cur != NULL; cur = cur->next) {
535 msginfo = (MsgInfo *)cur->data;
536
537 count++;
538 if (src->folder->ui_func)
539 src->folder->ui_func(src->folder, src, src->folder->ui_func_data ? src->folder->ui_func_data : GUINT_TO_POINTER(count));
540 if (folder_call_ui_func2(src->folder, src, count, length) == FALSE) {
541 debug_print("Export to mbox cancelled at %u/%u\n", count, length);
542 break;
543 }
544
545 msg_fp = procmsg_open_message(msginfo);
546 if (!msg_fp)
547 continue;
548
549 strncpy2(buf,
550 msginfo->from ? msginfo->from :
551 cur_ac && cur_ac->address ?
552 cur_ac->address : "unknown",
553 sizeof(buf));
554 extract_address(buf);
555
556 date_t_ = msginfo->date_t;
557 fprintf(mbox_fp, "From %s %s", buf, ctime(&date_t_));
558
559 while (fgets(buf, sizeof(buf), msg_fp) != NULL) {
560 if (!strncmp(buf, "From ", 5))
561 fputc('>', mbox_fp);
562 fputs(buf, mbox_fp);
563 }
564 fputc('\n', mbox_fp);
565
566 fclose(msg_fp);
567 }
568
569 fclose(mbox_fp);
570
571 return 0;
572}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __MBOX_H__
21#define __MBOX_H__
22
23#include <glib.h>
24
25#include "folder.h"
26
27typedef enum {
28 LOCK_FILE,
29 LOCK_FLOCK
30} LockType;
31
32gint proc_mbox (FolderItem *dest,
33 const gchar *mbox,
34 GHashTable *folder_table);
35gint proc_mbox_full (FolderItem *dest,
36 const gchar *mbox,
37 GHashTable *folder_table,
38 gboolean apply_filter,
39 gboolean filter_junk);
40
41gint lock_mbox (const gchar *base,
42 LockType type);
43gint unlock_mbox (const gchar *base,
44 gint fd,
45 LockType type);
46gint copy_mbox (const gchar *src,
47 const gchar *dest);
48void empty_mbox (const gchar *mbox);
49
50gint export_to_mbox (FolderItem *src,
51 const gchar *mbox);
52gint export_msgs_to_mbox(FolderItem *src,
53 GSList *mlist,
54 const gchar *mbox);
55
56#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 @@
1/*
2
3 GNet API added by David Helder <dhelder@umich.edu> 2000-6-11. All
4 additions and changes placed in the public domain.
5
6 Files originally from: http://www.gxsnmp.org/CVS/gxsnmp/
7
8 Modified the prefix of functions to prevent conflict with original GNet.
9
10 */
11/*
12 * This code implements the MD5 message-digest algorithm.
13 * The algorithm is due to Ron Rivest. This code was
14 * written by Colin Plumb in 1993, no copyright is claimed.
15 * This code is in the public domain; do with it what you wish.
16 *
17 * Equivalent code is available from RSA Data Security, Inc.
18 * This code has been tested against that, and is equivalent,
19 * except that you don't need to include two pages of legalese
20 * with every copy.
21 *
22 * To compute the message digest of a chunk of bytes, declare an
23 * MD5Context structure, pass it to MD5Init, call MD5Update as
24 * needed on buffers full of bytes, and then call MD5Final, which
25 * will fill a supplied 16-byte array with the digest.
26 */
27
28#include "md5.h"
29#include <glib.h>
30#include <string.h>
31
32
33/* ************************************************************ */
34/* Code below is from Colin Plumb implementation */
35
36
37
38struct MD5Context {
39 guint32 buf[4];
40 guint32 bits[2];
41 guchar in[64];
42 int doByteReverse;
43};
44
45static void MD5Init(struct MD5Context *context);
46static void MD5Update(struct MD5Context *context, guchar const *buf,
47 guint len);
48static void MD5Final(guchar digest[16], struct MD5Context *context);
49static void MD5Transform(guint32 buf[4], guint32 const in[16]);
50
51/*
52 * This is needed to make RSAREF happy on some MS-DOS compilers.
53 */
54typedef struct MD5Context MD5_CTX;
55
56
57
58
59static void byteReverse(guint8 *buf, guint longs);
60
61/*
62 * Note: this code is harmless on little-endian machines.
63 */
64void
65byteReverse(guint8 *buf, guint longs)
66{
67 guint32 t;
68 do
69 {
70 t = (guint32) ((guint) buf[3] << 8 | buf[2]) << 16 |
71 ((guint) buf[1] << 8 | buf[0]);
72 *(guint32 *) buf = t;
73 buf += 4;
74 }
75 while (--longs);
76}
77
78/*
79 * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
80 * initialization constants.
81 */
82void
83MD5Init(struct MD5Context *ctx)
84{
85 ctx->buf[0] = 0x67452301;
86 ctx->buf[1] = 0xefcdab89;
87 ctx->buf[2] = 0x98badcfe;
88 ctx->buf[3] = 0x10325476;
89
90 ctx->bits[0] = 0;
91 ctx->bits[1] = 0;
92
93#if (G_BYTE_ORDER == G_BIG_ENDIAN)
94 ctx->doByteReverse = 1;
95#else
96 ctx->doByteReverse = 0;
97#endif
98}
99
100/*
101 * Update context to reflect the concatenation of another buffer full
102 * of bytes.
103 */
104void
105MD5Update(struct MD5Context *ctx, guint8 const *buf, guint len)
106{
107 guint32 t;
108
109 /* Update bitcount */
110
111 t = ctx->bits[0];
112 if ((ctx->bits[0] = t + ((guint32) len << 3)) < t)
113 ctx->bits[1]++; /* Carry from low to high */
114 ctx->bits[1] += len >> 29;
115
116 t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
117
118 /* Handle any leading odd-sized chunks */
119
120 if (t)
121 {
122 guint8 *p = (guint8 *) ctx->in + t;
123
124 t = 64 - t;
125 if (len < t)
126 {
127 g_memmove(p, buf, len);
128 return;
129 }
130 g_memmove(p, buf, t);
131 if (ctx->doByteReverse)
132 byteReverse(ctx->in, 16);
133 MD5Transform(ctx->buf, (guint32 *) ctx->in);
134 buf += t;
135 len -= t;
136 }
137 /* Process data in 64-byte chunks */
138
139 while (len >= 64)
140 {
141 g_memmove(ctx->in, buf, 64);
142 if (ctx->doByteReverse)
143 byteReverse(ctx->in, 16);
144 MD5Transform(ctx->buf, (guint32 *) ctx->in);
145 buf += 64;
146 len -= 64;
147 }
148
149 /* Handle any remaining bytes of data. */
150
151 g_memmove(ctx->in, buf, len);
152}
153
154/*
155 * Final wrapup - pad to 64-byte boundary with the bit pattern
156 * 1 0* (64-bit count of bits processed, MSB-first)
157 */
158void
159MD5Final(guint8 digest[16], struct MD5Context *ctx)
160{
161 guint count;
162 guint8 *p;
163
164 /* Compute number of bytes mod 64 */
165 count = (ctx->bits[0] >> 3) & 0x3F;
166
167 /* Set the first char of padding to 0x80. This is safe since there is
168 always at least one byte free */
169 p = ctx->in + count;
170 *p++ = 0x80;
171
172 /* Bytes of padding needed to make 64 bytes */
173 count = 64 - 1 - count;
174
175 /* Pad out to 56 mod 64 */
176 if (count < 8)
177 {
178 /* Two lots of padding: Pad the first block to 64 bytes */
179 memset(p, 0, count);
180 if (ctx->doByteReverse)
181 byteReverse(ctx->in, 16);
182 MD5Transform(ctx->buf, (guint32 *) ctx->in);
183
184 /* Now fill the next block with 56 bytes */
185 memset(ctx->in, 0, 56);
186 }
187 else
188 {
189 /* Pad block to 56 bytes */
190 memset(p, 0, count - 8);
191 }
192 if (ctx->doByteReverse)
193 byteReverse(ctx->in, 14);
194
195 /* Append length in bits and transform */
196 ((guint32 *) ctx->in)[14] = ctx->bits[0];
197 ((guint32 *) ctx->in)[15] = ctx->bits[1];
198
199 MD5Transform(ctx->buf, (guint32 *) ctx->in);
200 if (ctx->doByteReverse)
201 byteReverse((guint8 *) ctx->buf, 4);
202 g_memmove(digest, ctx->buf, 16);
203 memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */
204}
205
206/* The four core functions - F1 is optimized somewhat */
207
208/* #define F1(x, y, z) (x & y | ~x & z) */
209#define F1(x, y, z) (z ^ (x & (y ^ z)))
210#define F2(x, y, z) F1(z, x, y)
211#define F3(x, y, z) (x ^ y ^ z)
212#define F4(x, y, z) (y ^ (x | ~z))
213
214/* This is the central step in the MD5 algorithm. */
215#define MD5STEP(f, w, x, y, z, data, s) \
216 ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
217
218/*
219 * The core of the MD5 algorithm, this alters an existing MD5 hash to
220 * reflect the addition of 16 longwords of new data. MD5Update blocks
221 * the data and converts bytes into longwords for this routine.
222 */
223void
224MD5Transform(guint32 buf[4], guint32 const in[16])
225{
226 register guint32 a, b, c, d;
227
228 a = buf[0];
229 b = buf[1];
230 c = buf[2];
231 d = buf[3];
232
233 MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
234 MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
235 MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
236 MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
237 MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
238 MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
239 MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
240 MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
241 MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
242 MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
243 MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
244 MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
245 MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
246 MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
247 MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
248 MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
249
250 MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
251 MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
252 MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
253 MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
254 MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
255 MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
256 MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
257 MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
258 MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
259 MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
260 MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
261 MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
262 MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
263 MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
264 MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
265 MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
266
267 MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
268 MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
269 MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
270 MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
271 MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
272 MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
273 MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
274 MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
275 MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
276 MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
277 MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
278 MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
279 MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
280 MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
281 MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
282 MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
283
284 MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
285 MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
286 MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
287 MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
288 MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
289 MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
290 MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
291 MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
292 MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
293 MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
294 MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
295 MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
296 MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
297 MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
298 MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
299 MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
300
301 buf[0] += a;
302 buf[1] += b;
303 buf[2] += c;
304 buf[3] += d;
305}
306
307
308
309/* ************************************************************ */
310/* Code below is David Helder's API for GNet */
311
312struct _SMD5
313{
314 struct MD5Context ctx;
315 gchar digest[S_GNET_MD5_HASH_LENGTH];
316};
317
318
319/**
320 * s_gnet_md5_new:
321 * @buffer: buffer to hash
322 * @length: length of @buffer
323 *
324 * Creates a #SMD5 from @buffer.
325 *
326 * Returns: a new #SMD5.
327 *
328 **/
329SMD5*
330s_gnet_md5_new (const guchar* buffer, guint length)
331{
332 SMD5* md5;
333
334 md5 = g_new0 (SMD5, 1);
335 MD5Init (&md5->ctx);
336 MD5Update (&md5->ctx, buffer, length);
337 MD5Final ((gpointer) &md5->digest, &md5->ctx);
338
339 return md5;
340}
341
342
343
344/**
345 * s_gnet_md5_new_string:
346 * @str: hexidecimal string
347 *
348 * Creates a #SMD5 from @str. @str is a hexidecimal string
349 * representing the digest.
350 *
351 * Returns: a new #SMD5.
352 *
353 **/
354SMD5*
355s_gnet_md5_new_string (const gchar* str)
356{
357 SMD5* md5;
358 guint i;
359
360 g_return_val_if_fail (str, NULL);
361 g_return_val_if_fail (strlen(str) >= (S_GNET_MD5_HASH_LENGTH * 2), NULL);
362
363 md5 = g_new0 (SMD5, 1);
364
365 for (i = 0; i < (S_GNET_MD5_HASH_LENGTH * 2); ++i)
366 {
367 guint val = 0;
368
369 switch (str[i])
370 {
371 case '0': val = 0; break;
372 case '1': val = 1; break;
373 case '2': val = 2; break;
374 case '3': val = 3; break;
375 case '4': val = 4; break;
376 case '5': val = 5; break;
377 case '6': val = 6; break;
378 case '7': val = 7; break;
379 case '8': val = 8; break;
380 case '9': val = 9; break;
381 case 'A':
382 case 'a': val = 10; break;
383 case 'B':
384 case 'b': val = 11; break;
385 case 'C':
386 case 'c': val = 12; break;
387 case 'D':
388 case 'd': val = 13; break;
389 case 'E':
390 case 'e': val = 14; break;
391 case 'F':
392 case 'f': val = 15; break;
393 default:
394 g_return_val_if_fail (FALSE, NULL);
395 }
396
397 if (i % 2)
398 md5->digest[i / 2] |= val;
399 else
400 md5->digest[i / 2] = val << 4;
401 }
402
403 return md5;
404}
405
406
407
408/**
409 * s_gnet_md5_clone
410 * @md5: a #SMD5
411 *
412 * Copies a #SMD5.
413 *
414 * Returns: a copy of @md5.
415 *
416 **/
417SMD5*
418s_gnet_md5_clone (const SMD5* md5)
419{
420 SMD5* md52;
421
422 g_return_val_if_fail (md5, NULL);
423
424 md52 = g_new0 (SMD5, 1);
425 md52->ctx = md5->ctx;
426 memcpy (md52->digest, md5->digest, sizeof(md5->digest));
427
428 return md52;
429}
430
431
432
433/**
434 * s_gnet_md5_delete
435 * @md5: a #SMD5
436 *
437 * Deletes a #SMD5.
438 *
439 **/
440void
441s_gnet_md5_delete (SMD5* md5)
442{
443 if (md5)
444 g_free (md5);
445}
446
447
448
449/**
450 * s_gnet_md5_new_incremental
451 *
452 * Creates a #SMD5 incrementally. After creating a #SMD5, call
453 * s_gnet_md5_update() one or more times to hash data. Finally, call
454 * s_gnet_md5_final() to compute the final hash value.
455 *
456 * Returns: a new #SMD5.
457 *
458 **/
459SMD5*
460s_gnet_md5_new_incremental (void)
461{
462 SMD5* md5;
463
464 md5 = g_new0 (SMD5, 1);
465 MD5Init (&md5->ctx);
466 return md5;
467}
468
469
470/**
471 * s_gnet_md5_update
472 * @md5: a #SMD5
473 * @buffer: buffer to add
474 * @length: length of @buffer
475 *
476 * Updates the hash with @buffer. This may be called several times
477 * on a hash created by s_gnet_md5_new_incremental() before being
478 * finalized by calling s_gnet_md5_final().
479 *
480 **/
481void
482s_gnet_md5_update (SMD5* md5, const guchar* buffer, guint length)
483{
484 g_return_if_fail (md5);
485
486 MD5Update (&md5->ctx, buffer, length);
487}
488
489
490/**
491 * s_gnet_md5_final
492 * @md5: a #SMD5
493 *
494 * Calcuates the final hash value of a #SMD5. This should only be
495 * called on an #SMD5 created by s_gnet_md5_new_incremental().
496 *
497 **/
498void
499s_gnet_md5_final (SMD5* md5)
500{
501 g_return_if_fail (md5);
502
503 MD5Final ((gpointer) &md5->digest, &md5->ctx);
504}
505
506
507/* **************************************** */
508
509/**
510 * s_gnet_md5_equal
511 * @p1: first #SMD5.
512 * @p2: second #SMD5.
513 *
514 * Compares two #SMD5's for equality.
515 *
516 * Returns: TRUE if they are equal; FALSE otherwise.
517 *
518 **/
519gint
520s_gnet_md5_equal (gconstpointer p1, gconstpointer p2)
521{
522 SMD5* md5a = (SMD5*) p1;
523 SMD5* md5b = (SMD5*) p2;
524 guint i;
525
526 for (i = 0; i < S_GNET_MD5_HASH_LENGTH; ++i)
527 if (md5a->digest[i] != md5b->digest[i])
528 return FALSE;
529
530 return TRUE;
531}
532
533
534/**
535 * s_gnet_md5_hash
536 * @p: a #SMD5
537 *
538 * Creates a hash code for a #SMD5 for use with GHashTable. This
539 * hash value is not the same as the MD5 digest.
540 *
541 * Returns: the hash code for @p.
542 *
543 **/
544guint
545s_gnet_md5_hash (gconstpointer p)
546{
547 const SMD5* md5 = (const SMD5*) p;
548 const guint* q;
549
550 g_return_val_if_fail (md5, 0);
551
552 q = (const guint*) md5->digest;
553
554 return (q[0] ^ q[1] ^ q[2] ^ q[3]);
555}
556
557
558/**
559 * s_gnet_md5_get_digest
560 * @md5: a #SMD5
561 *
562 * Gets the raw MD5 digest.
563 *
564 * Returns: a callee-owned buffer containing the MD5 hash digest.
565 * The buffer is %S_GNET_MD5_HASH_LENGTH bytes long.
566 *
567 **/
568gchar*
569s_gnet_md5_get_digest (const SMD5* md5)
570{
571 g_return_val_if_fail (md5, NULL);
572
573 return (gchar*) md5->digest;
574}
575
576
577static gchar bits2hex[16] = { '0', '1', '2', '3',
578 '4', '5', '6', '7',
579 '8', '9', 'a', 'b',
580 'c', 'd', 'e', 'f' };
581
582/**
583 * s_gnet_md5_get_string
584 * @md5: a #SMD5
585 *
586 * Gets the digest represented a human-readable string.
587 *
588 * Returns: a hexadecimal string representing the digest. The string
589 * is 2 * %S_GNET_MD5_HASH_LENGTH bytes long and NULL terminated. The
590 * string is caller owned.
591 *
592 **/
593gchar*
594s_gnet_md5_get_string (const SMD5* md5)
595{
596 gchar* str;
597 guint i;
598
599 g_return_val_if_fail (md5, NULL);
600
601 str = g_new (gchar, S_GNET_MD5_HASH_LENGTH * 2 + 1);
602 str[S_GNET_MD5_HASH_LENGTH * 2] = '\0';
603
604 for (i = 0; i < S_GNET_MD5_HASH_LENGTH; ++i)
605 {
606 str[i * 2] = bits2hex[(md5->digest[i] & 0xF0) >> 4];
607 str[(i * 2) + 1] = bits2hex[(md5->digest[i] & 0x0F) ];
608 }
609
610 return str;
611}
612
613
614
615/**
616 * s_gnet_md5_copy_string
617 * @md5: a #SMD5
618 * @buffer: buffer at least 2 * %S_GNET_MD5_HASH_LENGTH bytes long
619 *
620 * Copies the digest, represented as a string, into @buffer. The
621 * string is not NULL terminated.
622 *
623 **/
624void
625s_gnet_md5_copy_string (const SMD5* md5, gchar* buffer)
626{
627 guint i;
628
629 g_return_if_fail (md5);
630 g_return_if_fail (buffer);
631
632 for (i = 0; i < S_GNET_MD5_HASH_LENGTH; ++i)
633 {
634 buffer[i * 2] = bits2hex[(md5->digest[i] & 0xF0) >> 4];
635 buffer[(i * 2) + 1] = bits2hex[(md5->digest[i] & 0x0F) ];
636 }
637}
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 @@
1/**
2
3 This code is in the public domain. See md5.c for details.
4
5 Authors:
6 Colin Plumb [original author]
7 David Helder [GNet API]
8
9 Modified the prefix of functions to prevent conflict with original GNet.
10
11 */
12
13
14#ifndef S_GNET_MD5_H
15#define S_GNET_MD5_H
16
17#include <glib.h>
18
19#ifdef __cplusplus
20extern "C" {
21#endif /* __cplusplus */
22
23/**
24 * SMD5
25 *
26 * SMD5 is a MD5 hash.
27 *
28 **/
29typedef struct _SMD5 SMD5;
30
31/**
32 * S_GNET_MD5_HASH_LENGTH
33 *
34 * Length of the MD5 hash in bytes.
35 **/
36#define S_GNET_MD5_HASH_LENGTH 16
37
38
39SMD5* s_gnet_md5_new (const guchar* buffer, guint length);
40SMD5* s_gnet_md5_new_string (const gchar* str);
41SMD5* s_gnet_md5_clone (const SMD5* md5);
42void s_gnet_md5_delete (SMD5* md5);
43
44SMD5* s_gnet_md5_new_incremental (void);
45void s_gnet_md5_update (SMD5* md5, const guchar* buffer, guint length);
46void s_gnet_md5_final (SMD5* md5);
47
48gboolean s_gnet_md5_equal (gconstpointer p1, gconstpointer p2);
49guint s_gnet_md5_hash (gconstpointer p);
50
51gchar* s_gnet_md5_get_digest (const SMD5* md5);
52gchar* s_gnet_md5_get_string (const SMD5* md5);
53
54void s_gnet_md5_copy_string (const SMD5* md5, gchar* buffer);
55
56
57#ifdef __cplusplus
58}
59#endif /* __cplusplus */
60
61#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2006 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include <glib.h>
25#include <string.h>
26
27#include "md5.h"
28#include "md5_hmac.h"
29
30/*
31** Function: md5_hmac_get
32** taken from the file rfc2104.txt
33** originally written by Martin Schaaf <mascha@ma-scha.de>
34** rewritten by Hiroyuki Yamamoto <hiro-y@kcn.ne.jp>
35*/
36static SMD5*
37md5_hmac_get(const guchar *text, gint text_len,
38 const guchar *key, gint key_len)
39{
40 SMD5 *md5;
41 guchar k_ipad[64]; /* inner padding -
42 * key XORd with ipad
43 */
44 guchar k_opad[64]; /* outer padding -
45 * key XORd with opad
46 */
47 guchar digest[S_GNET_MD5_HASH_LENGTH];
48 gint i;
49
50 /* start out by storing key in pads */
51 memset(k_ipad, 0, sizeof k_ipad);
52 memset(k_opad, 0, sizeof k_opad);
53
54 if (key_len > 64) {
55 /* if key is longer than 64 bytes reset it to key=MD5(key) */
56 SMD5 *tmd5;
57
58 tmd5 = s_gnet_md5_new(key, key_len);
59 memcpy(k_ipad, s_gnet_md5_get_digest(tmd5),
60 S_GNET_MD5_HASH_LENGTH);
61 memcpy(k_opad, s_gnet_md5_get_digest(tmd5),
62 S_GNET_MD5_HASH_LENGTH);
63 s_gnet_md5_delete(tmd5);
64 } else {
65 memcpy(k_ipad, key, key_len);
66 memcpy(k_opad, key, key_len);
67 }
68
69 /*
70 * the HMAC_MD5 transform looks like:
71 *
72 * MD5(K XOR opad, MD5(K XOR ipad, text))
73 *
74 * where K is an n byte key
75 * ipad is the byte 0x36 repeated 64 times
76 * opad is the byte 0x5c repeated 64 times
77 * and text is the data being protected
78 */
79
80
81 /* XOR key with ipad and opad values */
82 for (i = 0; i < 64; i++) {
83 k_ipad[i] ^= 0x36;
84 k_opad[i] ^= 0x5c;
85 }
86
87 /*
88 * perform inner MD5
89 */
90 md5 = s_gnet_md5_new_incremental(); /* init context for 1st
91 * pass */
92 s_gnet_md5_update(md5, k_ipad, 64); /* start with inner pad */
93 s_gnet_md5_update(md5, text, text_len); /* then text of datagram */
94 s_gnet_md5_final(md5); /* finish up 1st pass */
95 memcpy(digest, s_gnet_md5_get_digest(md5), S_GNET_MD5_HASH_LENGTH);
96 s_gnet_md5_delete(md5);
97
98 /*
99 * perform outer MD5
100 */
101 md5 = s_gnet_md5_new_incremental(); /* init context for 2nd
102 * pass */
103 s_gnet_md5_update(md5, k_opad, 64); /* start with outer pad */
104 s_gnet_md5_update(md5, digest, 16); /* then results of 1st
105 * hash */
106 s_gnet_md5_final(md5); /* finish up 2nd pass */
107
108 return md5;
109}
110
111void
112md5_hmac(guchar *digest,
113 const guchar *text, gint text_len,
114 const guchar *key, gint key_len)
115{
116 SMD5 *md5;
117
118 md5 = md5_hmac_get(text, text_len, key, key_len);
119 memcpy(digest, s_gnet_md5_get_digest(md5), S_GNET_MD5_HASH_LENGTH);
120 s_gnet_md5_delete(md5);
121}
122
123void
124md5_hex_hmac(gchar *hexdigest,
125 const guchar *text, gint text_len,
126 const guchar *key, gint key_len)
127{
128 SMD5 *md5;
129
130 md5 = md5_hmac_get(text, text_len, key, key_len);
131 s_gnet_md5_copy_string(md5, hexdigest);
132 hexdigest[S_GNET_MD5_HASH_LENGTH * 2] = '\0';
133 s_gnet_md5_delete(md5);
134}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __MD5_HMAC_H__
21#define __MD5_HMAC_H__
22
23#include <glib.h>
24
25void md5_hmac (guchar *digest,
26 const guchar* text, gint text_len,
27 const guchar* key, gint key_len);
28void md5_hex_hmac (gchar *hexdigest,
29 const guchar* text, gint text_len,
30 const guchar* key, gint key_len);
31
32#endif /* __MD5_HMAC_H__ */
33
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2009 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <dirent.h>
29#include <sys/stat.h>
30#include <time.h>
31#include <unistd.h>
32#include <string.h>
33#include <errno.h>
34
35#ifdef G_OS_WIN32
36# include <windows.h>
37#endif
38
39#undef MEASURE_TIME
40
41#include "sylmain.h"
42#include "folder.h"
43#include "mh.h"
44#include "procmsg.h"
45#include "procheader.h"
46#include "utils.h"
47#include "prefs_common.h"
48
49#if USE_THREADS
50G_LOCK_DEFINE_STATIC(mh);
51#define S_LOCK(name) G_LOCK(name)
52#define S_UNLOCK(name) G_UNLOCK(name)
53#else
54#define S_LOCK(name)
55#define S_UNLOCK(name)
56#endif
57
58static void mh_folder_init (Folder *folder,
59 const gchar *name,
60 const gchar *path);
61
62static Folder *mh_folder_new (const gchar *name,
63 const gchar *path);
64static void mh_folder_destroy (Folder *folder);
65
66static GSList *mh_get_msg_list (Folder *folder,
67 FolderItem *item,
68 gboolean use_cache);
69static GSList *mh_get_uncached_msg_list(Folder *folder,
70 FolderItem *item);
71static gchar *mh_fetch_msg (Folder *folder,
72 FolderItem *item,
73 gint num);
74static MsgInfo *mh_get_msginfo (Folder *folder,
75 FolderItem *item,
76 gint num);
77static gint mh_add_msg (Folder *folder,
78 FolderItem *dest,
79 const gchar *file,
80 MsgFlags *flags,
81 gboolean remove_source);
82static gint mh_add_msgs (Folder *folder,
83 FolderItem *dest,
84 GSList *file_list,
85 gboolean remove_source,
86 gint *first);
87static gint mh_add_msg_msginfo (Folder *folder,
88 FolderItem *dest,
89 MsgInfo *msginfo,
90 gboolean remove_source);
91static gint mh_add_msgs_msginfo (Folder *folder,
92 FolderItem *dest,
93 GSList *msglist,
94 gboolean remove_source,
95 gint *first);
96static gint mh_move_msg (Folder *folder,
97 FolderItem *dest,
98 MsgInfo *msginfo);
99static gint mh_move_msgs (Folder *folder,
100 FolderItem *dest,
101 GSList *msglist);
102static gint mh_copy_msg (Folder *folder,
103 FolderItem *dest,
104 MsgInfo *msginfo);
105static gint mh_copy_msgs (Folder *folder,
106 FolderItem *dest,
107 GSList *msglist);
108static gint mh_remove_msg (Folder *folder,
109 FolderItem *item,
110 MsgInfo *msginfo);
111static gint mh_remove_all_msg (Folder *folder,
112 FolderItem *item);
113static gboolean mh_is_msg_changed (Folder *folder,
114 FolderItem *item,
115 MsgInfo *msginfo);
116static gint mh_close (Folder *folder,
117 FolderItem *item);
118
119static gint mh_scan_folder_full (Folder *folder,
120 FolderItem *item,
121 gboolean count_sum);
122static gint mh_scan_folder (Folder *folder,
123 FolderItem *item);
124static gint mh_scan_tree (Folder *folder);
125
126static gint mh_create_tree (Folder *folder);
127static FolderItem *mh_create_folder (Folder *folder,
128 FolderItem *parent,
129 const gchar *name);
130static gint mh_rename_folder (Folder *folder,
131 FolderItem *item,
132 const gchar *name);
133static gint mh_move_folder (Folder *folder,
134 FolderItem *item,
135 FolderItem *new_parent);
136static gint mh_remove_folder (Folder *folder,
137 FolderItem *item);
138
139static gchar *mh_get_new_msg_filename (FolderItem *dest);
140
141static gint mh_do_move_msgs (Folder *folder,
142 FolderItem *dest,
143 GSList *msglist);
144
145static time_t mh_get_mtime (FolderItem *item);
146static GSList *mh_get_uncached_msgs (GHashTable *msg_table,
147 FolderItem *item);
148static MsgInfo *mh_parse_msg (const gchar *file,
149 FolderItem *item);
150static void mh_remove_missing_folder_items (Folder *folder);
151static void mh_scan_tree_recursive (FolderItem *item);
152
153static gboolean mh_rename_folder_func (GNode *node,
154 gpointer data);
155
156static FolderClass mh_class =
157{
158 F_MH,
159
160 mh_folder_new,
161 mh_folder_destroy,
162
163 mh_scan_tree,
164 mh_create_tree,
165
166 mh_get_msg_list,
167 mh_get_uncached_msg_list,
168 mh_fetch_msg,
169 mh_get_msginfo,
170 mh_add_msg,
171 mh_add_msgs,
172 mh_add_msg_msginfo,
173 mh_add_msgs_msginfo,
174 mh_move_msg,
175 mh_move_msgs,
176 mh_copy_msg,
177 mh_copy_msgs,
178 mh_remove_msg,
179 NULL,
180 mh_remove_all_msg,
181 mh_is_msg_changed,
182 mh_close,
183 mh_scan_folder,
184
185 mh_create_folder,
186 mh_rename_folder,
187 mh_move_folder,
188 mh_remove_folder,
189};
190
191
192FolderClass *mh_get_class(void)
193{
194 return &mh_class;
195}
196
197static Folder *mh_folder_new(const gchar *name, const gchar *path)
198{
199 Folder *folder;
200
201 folder = (Folder *)g_new0(MHFolder, 1);
202 mh_folder_init(folder, name, path);
203
204 return folder;
205}
206
207static void mh_folder_destroy(Folder *folder)
208{
209 folder_local_folder_destroy(LOCAL_FOLDER(folder));
210}
211
212static void mh_folder_init(Folder *folder, const gchar *name, const gchar *path)
213{
214 folder->klass = mh_get_class();
215 folder_local_folder_init(folder, name, path);
216}
217
218static GSList *mh_get_msg_list_full(Folder *folder, FolderItem *item,
219 gboolean use_cache, gboolean uncached_only)
220{
221 GSList *mlist;
222 GHashTable *msg_table;
223 time_t cur_mtime;
224 GSList *newlist = NULL;
225#ifdef MEASURE_TIME
226 GTimer *timer;
227#endif
228
229 g_return_val_if_fail(item != NULL, NULL);
230
231 S_LOCK(mh);
232
233#ifdef MEASURE_TIME
234 timer = g_timer_new();
235#endif
236
237 cur_mtime = mh_get_mtime(item);
238
239 if (use_cache && item->mtime == cur_mtime) {
240 debug_print("Folder is not modified.\n");
241 mlist = procmsg_read_cache(item, FALSE);
242 if (!mlist) {
243 mlist = mh_get_uncached_msgs(NULL, item);
244 if (mlist)
245 item->cache_dirty = TRUE;
246 }
247 } else if (use_cache) {
248 GSList *cur, *next;
249 gboolean strict_cache_check = prefs_common.strict_cache_check;
250
251 if (item->stype == F_QUEUE || item->stype == F_DRAFT)
252 strict_cache_check = TRUE;
253
254 mlist = procmsg_read_cache(item, strict_cache_check);
255 msg_table = procmsg_msg_hash_table_create(mlist);
256 newlist = mh_get_uncached_msgs(msg_table, item);
257 if (newlist)
258 item->cache_dirty = TRUE;
259 if (msg_table)
260 g_hash_table_destroy(msg_table);
261
262 if (!strict_cache_check) {
263 /* remove nonexistent messages */
264 for (cur = mlist; cur != NULL; cur = next) {
265 MsgInfo *msginfo = (MsgInfo *)cur->data;
266 next = cur->next;
267 if (!MSG_IS_CACHED(msginfo->flags)) {
268 debug_print("removing nonexistent message %d from cache\n", msginfo->msgnum);
269 mlist = g_slist_remove(mlist, msginfo);
270 procmsg_msginfo_free(msginfo);
271 item->cache_dirty = TRUE;
272 item->mark_dirty = TRUE;
273 }
274 }
275 }
276
277 mlist = g_slist_concat(mlist, newlist);
278 } else {
279 mlist = mh_get_uncached_msgs(NULL, item);
280 item->cache_dirty = TRUE;
281 newlist = mlist;
282 }
283
284 procmsg_set_flags(mlist, item);
285
286 if (!uncached_only)
287 mlist = procmsg_sort_msg_list(mlist, item->sort_key,
288 item->sort_type);
289
290 if (item->mark_queue)
291 item->mark_dirty = TRUE;
292
293#ifdef MEASURE_TIME
294 g_timer_stop(timer);
295 g_print("%s: %s: elapsed time: %f sec\n",
296 G_STRFUNC, item->path, g_timer_elapsed(timer, NULL));
297 g_timer_destroy(timer);
298#endif
299 debug_print("cache_dirty: %d, mark_dirty: %d\n",
300 item->cache_dirty, item->mark_dirty);
301
302 if (!item->opened) {
303 item->mtime = cur_mtime;
304 if (item->cache_dirty)
305 procmsg_write_cache_list(item, mlist);
306 if (item->mark_dirty)
307 procmsg_write_flags_list(item, mlist);
308 }
309
310 if (uncached_only) {
311 GSList *cur;
312
313 if (newlist == NULL) {
314 procmsg_msg_list_free(mlist);
315 S_UNLOCK(mh);
316 return NULL;
317 }
318 if (mlist == newlist) {
319 S_UNLOCK(mh);
320 return newlist;
321 }
322 for (cur = mlist; cur != NULL; cur = cur->next) {
323 if (cur->next == newlist) {
324 cur->next = NULL;
325 procmsg_msg_list_free(mlist);
326 S_UNLOCK(mh);
327 return newlist;
328 }
329 }
330 procmsg_msg_list_free(mlist);
331 S_UNLOCK(mh);
332 return NULL;
333 }
334
335 S_UNLOCK(mh);
336 return mlist;
337}
338
339static GSList *mh_get_msg_list(Folder *folder, FolderItem *item,
340 gboolean use_cache)
341{
342 return mh_get_msg_list_full(folder, item, use_cache, FALSE);
343}
344
345static GSList *mh_get_uncached_msg_list(Folder *folder, FolderItem *item)
346{
347 return mh_get_msg_list_full(folder, item, TRUE, TRUE);
348}
349
350static gchar *mh_fetch_msg(Folder *folder, FolderItem *item, gint num)
351{
352 gchar *path;
353 gchar *file;
354 gchar buf[16];
355
356 g_return_val_if_fail(item != NULL, NULL);
357 g_return_val_if_fail(num > 0, NULL);
358
359 if (item->last_num < 0 || num > item->last_num) {
360 mh_scan_folder(folder, item);
361 if (item->last_num < 0) return NULL;
362 }
363
364 if (num > item->last_num)
365 return NULL;
366
367 path = folder_item_get_path(item);
368 file = g_strconcat(path, G_DIR_SEPARATOR_S, utos_buf(buf, num), NULL);
369 g_free(path);
370 if (!is_file_exist(file)) {
371 g_free(file);
372 return NULL;
373 }
374
375 return file;
376}
377
378static MsgInfo *mh_get_msginfo(Folder *folder, FolderItem *item, gint num)
379{
380 MsgInfo *msginfo;
381 gchar *file;
382
383 g_return_val_if_fail(item != NULL, NULL);
384 g_return_val_if_fail(num > 0, NULL);
385
386 file = mh_fetch_msg(folder, item, num);
387 if (!file) return NULL;
388
389 msginfo = mh_parse_msg(file, item);
390 if (msginfo)
391 msginfo->msgnum = num;
392
393 g_free(file);
394
395 return msginfo;
396}
397
398static gchar *mh_get_new_msg_filename(FolderItem *dest)
399{
400 gchar *destfile;
401 gchar *destpath;
402
403 destpath = folder_item_get_path(dest);
404 g_return_val_if_fail(destpath != NULL, NULL);
405
406 if (!is_dir_exist(destpath))
407 make_dir_hier(destpath);
408
409 for (;;) {
410 destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR,
411 dest->last_num + 1);
412 if (is_file_entry_exist(destfile)) {
413 dest->last_num++;
414 g_free(destfile);
415 } else
416 break;
417 }
418
419 g_free(destpath);
420
421 return destfile;
422}
423
424#define SET_DEST_MSG_FLAGS(fp, dest, n, fl) \
425{ \
426 MsgInfo newmsginfo; \
427 \
428 newmsginfo.msgnum = n; \
429 newmsginfo.flags = fl; \
430 if (dest->stype == F_OUTBOX || \
431 dest->stype == F_QUEUE || \
432 dest->stype == F_DRAFT) { \
433 MSG_UNSET_PERM_FLAGS(newmsginfo.flags, \
434 MSG_NEW|MSG_UNREAD|MSG_DELETED); \
435 } else if (dest->stype == F_TRASH) { \
436 MSG_UNSET_PERM_FLAGS(newmsginfo.flags, MSG_DELETED); \
437 } \
438 \
439 if (fp) \
440 procmsg_write_flags(&newmsginfo, fp); \
441 else \
442 procmsg_add_mark_queue(dest, n, newmsginfo.flags); \
443}
444
445static gint mh_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
446 MsgFlags *flags, gboolean remove_source)
447{
448 GSList file_list;
449 MsgFileInfo fileinfo;
450
451 g_return_val_if_fail(file != NULL, -1);
452
453 fileinfo.file = (gchar *)file;
454 fileinfo.flags = flags;
455 file_list.data = &fileinfo;
456 file_list.next = NULL;
457
458 return mh_add_msgs(folder, dest, &file_list, remove_source, NULL);
459}
460
461static gint mh_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
462 gboolean remove_source, gint *first)
463{
464 gchar *destfile;
465 GSList *cur;
466 MsgFileInfo *fileinfo;
467 MsgInfo *msginfo;
468 gint first_ = 0;
469 FILE *fp = NULL;
470
471 g_return_val_if_fail(dest != NULL, -1);
472 g_return_val_if_fail(file_list != NULL, -1);
473
474 if (dest->last_num < 0) {
475 mh_scan_folder(folder, dest);
476 if (dest->last_num < 0) return -1;
477 }
478
479 S_LOCK(mh);
480
481 if (!dest->opened) {
482 if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL)
483 g_warning("mh_add_msgs: can't open mark file.");
484 }
485
486 for (cur = file_list; cur != NULL; cur = cur->next) {
487 MsgFlags flags = {MSG_NEW|MSG_UNREAD, 0};
488
489 fileinfo = (MsgFileInfo *)cur->data;
490 if (fileinfo->flags)
491 flags = *fileinfo->flags;
492 msginfo = procheader_parse_file(fileinfo->file, flags, 0);
493 if (!msginfo) {
494 if (fp) fclose(fp);
495 S_UNLOCK(mh);
496 return -1;
497 }
498
499 destfile = mh_get_new_msg_filename(dest);
500 if (destfile == NULL) {
501 S_UNLOCK(mh);
502 return -1;
503 }
504 if (first_ == 0 || first_ > dest->last_num + 1)
505 first_ = dest->last_num + 1;
506
507 if (syl_link(fileinfo->file, destfile) < 0) {
508 if (copy_file(fileinfo->file, destfile, TRUE) < 0) {
509 g_warning(_("can't copy message %s to %s\n"),
510 fileinfo->file, destfile);
511 g_free(destfile);
512 if (fp) fclose(fp);
513 S_UNLOCK(mh);
514 return -1;
515 }
516 }
517
518 if (syl_app_get())
519 g_signal_emit_by_name(syl_app_get(), "add-msg", dest, destfile, dest->last_num + 1);
520
521 g_free(destfile);
522 dest->last_num++;
523 dest->total++;
524 dest->updated = TRUE;
525 dest->mtime = 0;
526
527 if (MSG_IS_RECEIVED(flags)) {
528 /* resets new flags of existing messages on
529 received mode */
530 if (dest->unmarked_num == 0)
531 dest->new = 0;
532 dest->unmarked_num++;
533 procmsg_add_mark_queue(dest, dest->last_num, flags);
534 } else {
535 SET_DEST_MSG_FLAGS(fp, dest, dest->last_num, flags);
536 }
537 procmsg_add_cache_queue(dest, dest->last_num, msginfo);
538 if (MSG_IS_NEW(flags))
539 dest->new++;
540 if (MSG_IS_UNREAD(flags))
541 dest->unread++;
542 }
543
544 if (fp)
545 fclose(fp);
546
547 if (first)
548 *first = first_;
549
550 if (remove_source) {
551 for (cur = file_list; cur != NULL; cur = cur->next) {
552 fileinfo = (MsgFileInfo *)cur->data;
553 if (g_unlink(fileinfo->file) < 0)
554 FILE_OP_ERROR(fileinfo->file, "unlink");
555 }
556 }
557
558 S_UNLOCK(mh);
559 return dest->last_num;
560}
561
562
563static gint mh_add_msg_msginfo(Folder *folder, FolderItem *dest,
564 MsgInfo *msginfo, gboolean remove_source)
565{
566 GSList msglist;
567
568 g_return_val_if_fail(msginfo != NULL, -1);
569
570 msglist.data = msginfo;
571 msglist.next = NULL;
572
573 return mh_add_msgs_msginfo(folder, dest, &msglist, remove_source, NULL);
574}
575
576static gint mh_add_msgs_msginfo(Folder *folder, FolderItem *dest,
577 GSList *msglist, gboolean remove_source,
578 gint *first)
579{
580 GSList *cur;
581 MsgInfo *msginfo;
582 gchar *srcfile;
583 gchar *destfile;
584 gint first_ = 0;
585 FILE *fp = NULL;
586
587 g_return_val_if_fail(dest != NULL, -1);
588 g_return_val_if_fail(msglist != NULL, -1);
589
590 if (dest->last_num < 0) {
591 mh_scan_folder(folder, dest);
592 if (dest->last_num < 0) return -1;
593 }
594
595 S_LOCK(mh);
596
597 if (!dest->opened) {
598 if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL)
599 g_warning("mh_add_msgs_msginfo: can't open mark file.");
600 }
601
602 for (cur = msglist; cur != NULL; cur = cur->next) {
603 msginfo = (MsgInfo *)cur->data;
604
605 destfile = mh_get_new_msg_filename(dest);
606 if (!destfile) {
607 if (fp) fclose(fp);
608 S_UNLOCK(mh);
609 return -1;
610 }
611 if (first_ == 0 || first_ > dest->last_num + 1)
612 first_ = dest->last_num + 1;
613
614 srcfile = procmsg_get_message_file(msginfo);
615 if (!srcfile) {
616 if (fp) fclose(fp);
617 g_free(destfile);
618 S_UNLOCK(mh);
619 return -1;
620 }
621 if (syl_link(srcfile, destfile) < 0) {
622 if (copy_file(srcfile, destfile, TRUE) < 0) {
623 g_warning("mh_add_msgs_msginfo: can't copy message %s to %s", srcfile, destfile);
624 g_free(srcfile);
625 g_free(destfile);
626 if (fp) fclose(fp);
627 S_UNLOCK(mh);
628 return -1;
629 }
630 }
631
632 if (syl_app_get())
633 g_signal_emit_by_name(syl_app_get(), "add-msg", dest, destfile, dest->last_num + 1);
634
635 g_free(srcfile);
636 g_free(destfile);
637 dest->last_num++;
638 dest->total++;
639 dest->updated = TRUE;
640 dest->mtime = 0;
641
642 if (MSG_IS_RECEIVED(msginfo->flags)) {
643 /* resets new flags of existing messages on
644 received mode */
645 if (dest->unmarked_num == 0)
646 dest->new = 0;
647 dest->unmarked_num++;
648 procmsg_add_mark_queue(dest, dest->last_num,
649 msginfo->flags);
650 } else {
651 SET_DEST_MSG_FLAGS(fp, dest, dest->last_num,
652 msginfo->flags);
653 }
654 procmsg_add_cache_queue(dest, dest->last_num, msginfo);
655 if (MSG_IS_NEW(msginfo->flags))
656 dest->new++;
657 if (MSG_IS_UNREAD(msginfo->flags))
658 dest->unread++;
659 }
660
661 if (fp)
662 fclose(fp);
663
664 if (first)
665 *first = first_;
666
667 if (remove_source) {
668 for (cur = msglist; cur != NULL; cur = cur->next) {
669 msginfo = (MsgInfo *)cur->data;
670 srcfile = procmsg_get_message_file(msginfo);
671 if (g_unlink(srcfile) < 0)
672 FILE_OP_ERROR(srcfile, "unlink");
673 g_free(srcfile);
674 }
675 }
676
677 S_UNLOCK(mh);
678 return dest->last_num;
679}
680
681
682static gint mh_do_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
683{
684 FolderItem *src;
685 gchar *srcfile;
686 gchar *destfile;
687 GSList *cur;
688 MsgInfo *msginfo;
689
690 g_return_val_if_fail(dest != NULL, -1);
691 g_return_val_if_fail(msglist != NULL, -1);
692
693 if (dest->last_num < 0) {
694 mh_scan_folder(folder, dest);
695 if (dest->last_num < 0) return -1;
696 }
697
698 S_LOCK(mh);
699
700 for (cur = msglist; cur != NULL; cur = cur->next) {
701 msginfo = (MsgInfo *)cur->data;
702 src = msginfo->folder;
703
704 if (src == dest) {
705 g_warning(_("the src folder is identical to the dest.\n"));
706 continue;
707 }
708 debug_print("Moving message %s/%d to %s ...\n",
709 src->path, msginfo->msgnum, dest->path);
710
711 destfile = mh_get_new_msg_filename(dest);
712 if (!destfile) break;
713 srcfile = procmsg_get_message_file(msginfo);
714
715 /* g_signal_emit_by_name(syl_app_get(), "remove-msg", src, srcfile, msginfo->msgnum); */
716
717 if (move_file(srcfile, destfile, FALSE) < 0) {
718 g_free(srcfile);
719 g_free(destfile);
720 break;
721 }
722
723 if (syl_app_get()) {
724 g_signal_emit_by_name(syl_app_get(), "add-msg", dest, destfile, dest->last_num + 1);
725 g_signal_emit_by_name(syl_app_get(), "remove-msg", src, srcfile, msginfo->msgnum);
726 }
727
728 g_free(srcfile);
729 g_free(destfile);
730 src->total--;
731 src->updated = TRUE;
732 src->mtime = 0;
733 dest->last_num++;
734 dest->total++;
735 dest->updated = TRUE;
736 dest->mtime = 0;
737
738 SET_DEST_MSG_FLAGS(NULL, dest, dest->last_num, msginfo->flags);
739 procmsg_add_cache_queue(dest, dest->last_num, msginfo);
740
741 if (MSG_IS_NEW(msginfo->flags)) {
742 src->new--;
743 dest->new++;
744 }
745 if (MSG_IS_UNREAD(msginfo->flags)) {
746 src->unread--;
747 dest->unread++;
748 }
749
750 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID);
751 }
752
753 if (!dest->opened) {
754 procmsg_flush_mark_queue(dest, NULL);
755 procmsg_flush_cache_queue(dest, NULL);
756 }
757
758 S_UNLOCK(mh);
759 return dest->last_num;
760}
761
762static gint mh_move_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
763{
764 GSList msglist;
765
766 g_return_val_if_fail(msginfo != NULL, -1);
767
768 msglist.data = msginfo;
769 msglist.next = NULL;
770
771 return mh_move_msgs(folder, dest, &msglist);
772}
773
774static gint mh_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
775{
776 MsgInfo *msginfo;
777 gint ret = 0;
778 gint first;
779
780 msginfo = (MsgInfo *)msglist->data;
781 if (folder == msginfo->folder->folder)
782 return mh_do_move_msgs(folder, dest, msglist);
783
784 ret = mh_add_msgs_msginfo(folder, dest, msglist, FALSE, &first);
785
786 if (ret != -1)
787 ret = folder_item_remove_msgs(msginfo->folder, msglist);
788
789 return ret;
790}
791
792static gint mh_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
793{
794 GSList msglist;
795
796 g_return_val_if_fail(msginfo != NULL, -1);
797
798 msglist.data = msginfo;
799 msglist.next = NULL;
800
801 return mh_copy_msgs(folder, dest, &msglist);
802}
803
804static gint mh_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist)
805{
806 gchar *srcfile;
807 gchar *destfile;
808 GSList *cur;
809 MsgInfo *msginfo;
810
811 g_return_val_if_fail(dest != NULL, -1);
812 g_return_val_if_fail(msglist != NULL, -1);
813
814 if (dest->last_num < 0) {
815 mh_scan_folder(folder, dest);
816 if (dest->last_num < 0) return -1;
817 }
818
819 S_LOCK(mh);
820
821 for (cur = msglist; cur != NULL; cur = cur->next) {
822 msginfo = (MsgInfo *)cur->data;
823
824 if (msginfo->folder == dest) {
825 g_warning(_("the src folder is identical to the dest.\n"));
826 continue;
827 }
828 debug_print(_("Copying message %s/%d to %s ...\n"),
829 msginfo->folder->path, msginfo->msgnum, dest->path);
830
831 destfile = mh_get_new_msg_filename(dest);
832 if (!destfile) break;
833 srcfile = procmsg_get_message_file(msginfo);
834
835 if (copy_file(srcfile, destfile, TRUE) < 0) {
836 FILE_OP_ERROR(srcfile, "copy");
837 g_free(srcfile);
838 g_free(destfile);
839 break;
840 }
841
842 if (syl_app_get())
843 g_signal_emit_by_name(syl_app_get(), "add-msg", dest, destfile, dest->last_num + 1);
844
845 g_free(srcfile);
846 g_free(destfile);
847 dest->last_num++;
848 dest->total++;
849 dest->updated = TRUE;
850 dest->mtime = 0;
851
852 SET_DEST_MSG_FLAGS(NULL, dest, dest->last_num, msginfo->flags);
853 procmsg_add_cache_queue(dest, dest->last_num, msginfo);
854
855 if (MSG_IS_NEW(msginfo->flags))
856 dest->new++;
857 if (MSG_IS_UNREAD(msginfo->flags))
858 dest->unread++;
859 }
860
861 if (!dest->opened) {
862 procmsg_flush_mark_queue(dest, NULL);
863 procmsg_flush_cache_queue(dest, NULL);
864 }
865
866 S_UNLOCK(mh);
867 return dest->last_num;
868}
869
870static gint mh_remove_msg(Folder *folder, FolderItem *item, MsgInfo *msginfo)
871{
872 gchar *file;
873
874 g_return_val_if_fail(item != NULL, -1);
875
876 file = mh_fetch_msg(folder, item, msginfo->msgnum);
877 g_return_val_if_fail(file != NULL, -1);
878
879 if (syl_app_get())
880 g_signal_emit_by_name(syl_app_get(), "remove-msg", item, file, msginfo->msgnum);
881
882 S_LOCK(mh);
883
884 if (g_unlink(file) < 0) {
885 FILE_OP_ERROR(file, "unlink");
886 g_free(file);
887 S_UNLOCK(mh);
888 return -1;
889 }
890 g_free(file);
891
892 item->total--;
893 item->updated = TRUE;
894 item->mtime = 0;
895 if (MSG_IS_NEW(msginfo->flags))
896 item->new--;
897 if (MSG_IS_UNREAD(msginfo->flags))
898 item->unread--;
899 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID);
900
901 S_UNLOCK(mh);
902
903 if (msginfo->msgnum == item->last_num)
904 mh_scan_folder_full(folder, item, FALSE);
905
906 return 0;
907}
908
909static gint mh_remove_all_msg(Folder *folder, FolderItem *item)
910{
911 gchar *path;
912 gint val;
913
914 g_return_val_if_fail(item != NULL, -1);
915
916 path = folder_item_get_path(item);
917 g_return_val_if_fail(path != NULL, -1);
918 if (syl_app_get())
919 g_signal_emit_by_name(syl_app_get(), "remove-all-msg", item);
920
921 S_LOCK(mh);
922
923 val = remove_all_numbered_files(path);
924 g_free(path);
925 if (val == 0) {
926 item->new = item->unread = item->total = 0;
927 item->last_num = 0;
928 item->updated = TRUE;
929 item->mtime = 0;
930 }
931
932 S_UNLOCK(mh);
933
934 return val;
935}
936
937static gboolean mh_is_msg_changed(Folder *folder, FolderItem *item,
938 MsgInfo *msginfo)
939{
940 GStatBuf s;
941 gchar buf[16];
942
943 if (g_stat(utos_buf(buf, msginfo->msgnum), &s) < 0 ||
944 msginfo->size != s.st_size ||
945 msginfo->mtime != s.st_mtime)
946 return TRUE;
947
948 return FALSE;
949}
950
951static gint mh_close(Folder *folder, FolderItem *item)
952{
953 return 0;
954}
955
956#ifdef G_OS_WIN32
957struct wfddata {
958 WIN32_FIND_DATAA wfda;
959 WIN32_FIND_DATAW wfdw;
960 DWORD file_attr;
961 gchar *file_name;
962};
963
964static HANDLE find_first_file(const gchar *path, struct wfddata *wfd)
965{
966 HANDLE hfind;
967
968 if (G_WIN32_HAVE_WIDECHAR_API()) {
969 if (path) {
970 gchar *wildcard_path;
971 wchar_t *wpath;
972
973 wildcard_path = g_strconcat(path, "\\*", NULL);
974 wpath = g_utf8_to_utf16(wildcard_path, -1,
975 NULL, NULL, NULL);
976 if (wpath) {
977 hfind = FindFirstFileW(wpath, &wfd->wfdw);
978 g_free(wpath);
979 } else
980 hfind = INVALID_HANDLE_VALUE;
981 g_free(wildcard_path);
982 } else
983 hfind = FindFirstFileW(L"*", &wfd->wfdw);
984
985 if (hfind != INVALID_HANDLE_VALUE) {
986 wfd->file_attr = wfd->wfdw.dwFileAttributes;
987 wfd->file_name = g_utf16_to_utf8(wfd->wfdw.cFileName, -1,
988 NULL, NULL, NULL);
989 }
990 } else {
991 if (path) {
992 gchar *wildcard_path;
993 gchar *cp_path;
994
995 wildcard_path = g_strconcat(path, "\\*", NULL);
996 cp_path = g_locale_from_utf8(wildcard_path, -1,
997 NULL, NULL, NULL);
998 if (cp_path) {
999 hfind = FindFirstFileA(cp_path, &wfd->wfda);
1000 g_free(cp_path);
1001 } else
1002 hfind = INVALID_HANDLE_VALUE;
1003 g_free(wildcard_path);
1004 } else
1005 hfind = FindFirstFileA("*", &wfd->wfda);
1006
1007 if (hfind != INVALID_HANDLE_VALUE) {
1008 wfd->file_attr = wfd->wfda.dwFileAttributes;
1009 wfd->file_name = g_locale_to_utf8(wfd->wfda.cFileName,
1010 -1, NULL, NULL, NULL);
1011 }
1012 }
1013
1014 return hfind;
1015}
1016
1017static BOOL find_next_file(HANDLE hfind, struct wfddata *wfd)
1018{
1019 BOOL retval;
1020
1021 if (G_WIN32_HAVE_WIDECHAR_API()) {
1022 retval = FindNextFileW(hfind, &wfd->wfdw);
1023 if (retval) {
1024 wfd->file_attr = wfd->wfdw.dwFileAttributes;
1025 wfd->file_name = g_utf16_to_utf8(wfd->wfdw.cFileName, -1,
1026 NULL, NULL, NULL);
1027 }
1028 } else {
1029 retval = FindNextFileA(hfind, &wfd->wfda);
1030 if (retval) {
1031 wfd->file_attr = wfd->wfda.dwFileAttributes;
1032 wfd->file_name = g_locale_to_utf8(wfd->wfda.cFileName,
1033 -1, NULL, NULL, NULL);
1034 }
1035 }
1036
1037 return retval;
1038}
1039#endif
1040
1041static gint mh_scan_folder_full(Folder *folder, FolderItem *item,
1042 gboolean count_sum)
1043{
1044 gchar *path;
1045#ifdef G_OS_WIN32
1046 struct wfddata wfd;
1047 HANDLE hfind;
1048#else
1049 DIR *dp;
1050 struct dirent *d;
1051#endif
1052 gint max = 0;
1053 gint num;
1054 gint n_msg = 0;
1055
1056 g_return_val_if_fail(item != NULL, -1);
1057
1058 debug_print("mh_scan_folder(): Scanning %s ...\n", item->path);
1059
1060 S_LOCK(mh);
1061
1062 path = folder_item_get_path(item);
1063 if (!path) {
1064 S_UNLOCK(mh);
1065 return -1;
1066 }
1067 if (change_dir(path) < 0) {
1068 g_free(path);
1069 S_UNLOCK(mh);
1070 return -1;
1071 }
1072 g_free(path);
1073
1074#ifdef G_OS_WIN32
1075 if ((hfind = find_first_file(NULL, &wfd)) == INVALID_HANDLE_VALUE) {
1076 g_warning("failed to open directory\n");
1077#else
1078 if ((dp = opendir(".")) == NULL) {
1079 FILE_OP_ERROR(item->path, "opendir");
1080#endif
1081 S_UNLOCK(mh);
1082 return -1;
1083 }
1084
1085 if (folder->ui_func)
1086 folder->ui_func(folder, item, folder->ui_func_data);
1087
1088#ifdef G_OS_WIN32
1089 do {
1090 if ((wfd.file_attr &
1091 (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE)) == 0) {
1092 if (wfd.file_name) {
1093 if ((num = to_number(wfd.file_name)) > 0) {
1094 n_msg++;
1095 if (max < num)
1096 max = num;
1097 }
1098 }
1099 }
1100
1101 if (wfd.file_name) {
1102 g_free(wfd.file_name);
1103 wfd.file_name = NULL;
1104 }
1105 } while (find_next_file(hfind, &wfd));
1106
1107 FindClose(hfind);
1108#else
1109 while ((d = readdir(dp)) != NULL) {
1110 if ((num = to_number(d->d_name)) > 0 &&
1111 dirent_is_regular_file(d)) {
1112 n_msg++;
1113 if (max < num)
1114 max = num;
1115 }
1116 }
1117
1118 closedir(dp);
1119#endif
1120
1121 if (n_msg == 0)
1122 item->new = item->unread = item->total = 0;
1123 else if (count_sum) {
1124 gint new, unread, total, min, max_;
1125
1126 procmsg_get_mark_sum
1127 (item, &new, &unread, &total, &min, &max_, 0);
1128
1129 if (n_msg > total) {
1130 item->unmarked_num = new = n_msg - total;
1131 unread += n_msg - total;
1132 } else
1133 item->unmarked_num = 0;
1134
1135 item->new = new;
1136 item->unread = unread;
1137 item->total = n_msg;
1138
1139 if (item->cache_queue && !item->opened) {
1140 procmsg_flush_cache_queue(item, NULL);
1141 }
1142 }
1143
1144 item->updated = TRUE;
1145 item->mtime = 0;
1146
1147 debug_print("Last number in dir %s = %d\n", item->path, max);
1148 item->last_num = max;
1149
1150 S_UNLOCK(mh);
1151 return 0;
1152}
1153
1154static gint mh_scan_folder(Folder *folder, FolderItem *item)
1155{
1156 return mh_scan_folder_full(folder, item, TRUE);
1157}
1158
1159static gint mh_scan_tree(Folder *folder)
1160{
1161 FolderItem *item;
1162 gchar *rootpath;
1163
1164 g_return_val_if_fail(folder != NULL, -1);
1165
1166 S_LOCK(mh);
1167
1168 if (!folder->node) {
1169 item = folder_item_new(folder->name, NULL);
1170 item->folder = folder;
1171 folder->node = item->node = g_node_new(item);
1172 } else
1173 item = FOLDER_ITEM(folder->node->data);
1174
1175 rootpath = folder_item_get_path(item);
1176 if (change_dir(rootpath) < 0) {
1177 g_free(rootpath);
1178 S_UNLOCK(mh);
1179 return -1;
1180 }
1181 g_free(rootpath);
1182
1183 mh_create_tree(folder);
1184 mh_remove_missing_folder_items(folder);
1185 mh_scan_tree_recursive(item);
1186
1187 S_UNLOCK(mh);
1188 return 0;
1189}
1190
1191#define MAKE_DIR_IF_NOT_EXIST(dir) \
1192{ \
1193 if (!is_dir_exist(dir)) { \
1194 if (is_file_exist(dir)) { \
1195 g_warning(_("File `%s' already exists.\n" \
1196 "Can't create folder."), dir); \
1197 return -1; \
1198 } \
1199 if (make_dir(dir) < 0) \
1200 return -1; \
1201 } \
1202}
1203
1204#define MAKE_DIR_HIER_IF_NOT_EXIST(dir) \
1205{ \
1206 if (!is_dir_exist(dir)) { \
1207 if (is_file_exist(dir)) { \
1208 g_warning(_("File `%s' already exists.\n" \
1209 "Can't create folder."), dir); \
1210 return -1; \
1211 } \
1212 if (make_dir_hier(dir) < 0) \
1213 return -1; \
1214 } \
1215}
1216
1217static gint mh_create_tree(Folder *folder)
1218{
1219 gchar *rootpath;
1220
1221 g_return_val_if_fail(folder != NULL, -1);
1222
1223 CHDIR_RETURN_VAL_IF_FAIL(get_mail_base_dir(), -1);
1224 rootpath = LOCAL_FOLDER(folder)->rootpath;
1225 MAKE_DIR_HIER_IF_NOT_EXIST(rootpath);
1226 CHDIR_RETURN_VAL_IF_FAIL(rootpath, -1);
1227 MAKE_DIR_IF_NOT_EXIST(INBOX_DIR);
1228 MAKE_DIR_IF_NOT_EXIST(OUTBOX_DIR);
1229 MAKE_DIR_IF_NOT_EXIST(QUEUE_DIR);
1230 MAKE_DIR_IF_NOT_EXIST(DRAFT_DIR);
1231 MAKE_DIR_IF_NOT_EXIST(TRASH_DIR);
1232 MAKE_DIR_IF_NOT_EXIST(JUNK_DIR);
1233
1234 return 0;
1235}
1236
1237#undef MAKE_DIR_IF_NOT_EXIST
1238#undef MAKE_DIR_HIER_IF_NOT_EXIST
1239
1240static FolderItem *mh_create_folder(Folder *folder, FolderItem *parent,
1241 const gchar *name)
1242{
1243 gchar *path;
1244 gchar *fs_name;
1245 gchar *fullpath;
1246 FolderItem *new_item;
1247
1248 g_return_val_if_fail(folder != NULL, NULL);
1249 g_return_val_if_fail(parent != NULL, NULL);
1250 g_return_val_if_fail(name != NULL, NULL);
1251
1252 S_LOCK(mh);
1253
1254 path = folder_item_get_path(parent);
1255 fs_name = g_filename_from_utf8(name, -1, NULL, NULL, NULL);
1256 fullpath = g_strconcat(path, G_DIR_SEPARATOR_S,
1257 fs_name ? fs_name : name, NULL);
1258 g_free(fs_name);
1259 g_free(path);
1260
1261 if (make_dir_hier(fullpath) < 0) {
1262 g_free(fullpath);
1263 S_UNLOCK(mh);
1264 return NULL;
1265 }
1266
1267 g_free(fullpath);
1268
1269 /* path is a logical folder path */
1270 if (parent->path)
1271 path = g_strconcat(parent->path, "/", name, NULL);
1272 else
1273 path = g_strdup(name);
1274 new_item = folder_item_new(name, path);
1275 folder_item_append(parent, new_item);
1276 g_free(path);
1277
1278 S_UNLOCK(mh);
1279 return new_item;
1280}
1281
1282static gint mh_move_folder_real(Folder *folder, FolderItem *item,
1283 FolderItem *new_parent, const gchar *name)
1284{
1285 gchar *rootpath;
1286 gchar *oldpath;
1287 gchar *newpath;
1288 gchar *dirname;
1289 gchar *new_dir;
1290 gchar *name_;
1291 gchar *utf8_name;
1292 gchar *paths[2];
1293 gchar *old_id, *new_id;
1294
1295 g_return_val_if_fail(folder != NULL, -1);
1296 g_return_val_if_fail(item != NULL, -1);
1297 g_return_val_if_fail(folder == item->folder, -1);
1298 g_return_val_if_fail(item->path != NULL, -1);
1299 g_return_val_if_fail(new_parent != NULL || name != NULL, -1);
1300 if (new_parent) {
1301 g_return_val_if_fail(item != new_parent, -1);
1302 g_return_val_if_fail(item->parent != new_parent, -1);
1303 g_return_val_if_fail(item->folder == new_parent->folder, -1);
1304 if (g_node_is_ancestor(item->node, new_parent->node)) {
1305 g_warning("folder to be moved is ancestor of new parent\n");
1306 return -1;
1307 }
1308 }
1309
1310 S_LOCK(mh);
1311
1312 oldpath = folder_item_get_path(item);
1313 if (new_parent) {
1314 if (name) {
1315 name_ = g_filename_from_utf8(name, -1, NULL, NULL,
1316 NULL);
1317 if (!name_)
1318 name_ = g_strdup(name);
1319 utf8_name = g_strdup(name);
1320 } else {
1321 name_ = g_path_get_basename(oldpath);
1322 utf8_name = g_filename_to_utf8(name_, -1, NULL, NULL,
1323 NULL);
1324 if (!utf8_name)
1325 utf8_name = g_strdup(name_);
1326 }
1327 new_dir = folder_item_get_path(new_parent);
1328 newpath = g_strconcat(new_dir, G_DIR_SEPARATOR_S, name_, NULL);
1329 g_free(new_dir);
1330 } else {
1331 name_ = g_filename_from_utf8(name, -1, NULL, NULL, NULL);
1332 utf8_name = g_strdup(name);
1333 dirname = g_dirname(oldpath);
1334 newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S,
1335 name_ ? name_ : name, NULL);
1336 g_free(dirname);
1337 }
1338 g_free(name_);
1339
1340 if (is_file_entry_exist(newpath)) {
1341 g_warning("%s already exists\n", newpath);
1342 g_free(oldpath);
1343 g_free(newpath);
1344 g_free(utf8_name);
1345 S_UNLOCK(mh);
1346 return -1;
1347 }
1348
1349 rootpath = folder_get_path(folder);
1350 if (change_dir(rootpath) < 0) {
1351 g_free(rootpath);
1352 g_free(oldpath);
1353 g_free(newpath);
1354 g_free(utf8_name);
1355 S_UNLOCK(mh);
1356 return -1;
1357 }
1358 g_free(rootpath);
1359
1360 debug_print("mh_move_folder: rename(%s, %s)\n", oldpath, newpath);
1361
1362 if (g_rename(oldpath, newpath) < 0) {
1363 FILE_OP_ERROR(oldpath, "rename");
1364 g_free(oldpath);
1365 g_free(newpath);
1366 g_free(utf8_name);
1367 S_UNLOCK(mh);
1368 return -1;
1369 }
1370
1371 g_free(oldpath);
1372 g_free(newpath);
1373
1374 old_id = folder_item_get_identifier(item);
1375
1376 if (new_parent) {
1377 g_node_unlink(item->node);
1378 g_node_append(new_parent->node, item->node);
1379 item->parent = new_parent;
1380 if (new_parent->path != NULL) {
1381 newpath = g_strconcat(new_parent->path, "/", utf8_name,
1382 NULL);
1383 g_free(utf8_name);
1384 } else
1385 newpath = utf8_name;
1386 } else {
1387 if (strchr(item->path, '/') != NULL) {
1388 dirname = g_dirname(item->path);
1389 newpath = g_strconcat(dirname, "/", utf8_name, NULL);
1390 g_free(dirname);
1391 g_free(utf8_name);
1392 } else
1393 newpath = utf8_name;
1394 }
1395
1396 if (name) {
1397 g_free(item->name);
1398 item->name = g_strdup(name);
1399 }
1400
1401 paths[0] = g_strdup(item->path);
1402 paths[1] = newpath;
1403 g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1404 mh_rename_folder_func, paths);
1405
1406 g_free(paths[0]);
1407 g_free(paths[1]);
1408
1409 new_id = folder_item_get_identifier(item);
1410 if (syl_app_get())
1411 g_signal_emit_by_name(syl_app_get(), "move-folder", item,
1412 old_id, new_id);
1413 g_free(new_id);
1414 g_free(old_id);
1415
1416 S_UNLOCK(mh);
1417 return 0;
1418}
1419
1420static gint mh_move_folder(Folder *folder, FolderItem *item,
1421 FolderItem *new_parent)
1422{
1423 return mh_move_folder_real(folder, item, new_parent, NULL);
1424}
1425
1426static gint mh_rename_folder(Folder *folder, FolderItem *item,
1427 const gchar *name)
1428{
1429 return mh_move_folder_real(folder, item, NULL, name);
1430}
1431
1432static gint mh_remove_folder(Folder *folder, FolderItem *item)
1433{
1434 gchar *path;
1435
1436 g_return_val_if_fail(folder != NULL, -1);
1437 g_return_val_if_fail(item != NULL, -1);
1438 g_return_val_if_fail(item->path != NULL, -1);
1439
1440 S_LOCK(mh);
1441
1442 path = folder_item_get_path(item);
1443 if (remove_dir_recursive(path) < 0) {
1444 g_warning("can't remove directory `%s'\n", path);
1445 g_free(path);
1446 S_UNLOCK(mh);
1447 return -1;
1448 }
1449
1450 g_free(path);
1451 if (syl_app_get())
1452 g_signal_emit_by_name(syl_app_get(), "remove-folder", item);
1453 folder_item_remove(item);
1454
1455 S_UNLOCK(mh);
1456 return 0;
1457}
1458
1459
1460static time_t mh_get_mtime(FolderItem *item)
1461{
1462 gchar *path;
1463 GStatBuf s;
1464
1465 path = folder_item_get_path(item);
1466 if (g_stat(path, &s) < 0) {
1467 FILE_OP_ERROR(path, "stat");
1468 g_free(path);
1469 return -1;
1470 } else {
1471 g_free(path);
1472 return MAX(s.st_mtime, s.st_ctime);
1473 }
1474}
1475
1476static GSList *mh_get_uncached_msgs(GHashTable *msg_table, FolderItem *item)
1477{
1478 gchar *path;
1479 GDir *dp;
1480 const gchar *dir_name;
1481 GSList *newlist = NULL;
1482 GSList *last = NULL;
1483 MsgInfo *msginfo;
1484 gint n_newmsg = 0;
1485 gint num;
1486 Folder *folder;
1487
1488 g_return_val_if_fail(item != NULL, NULL);
1489 g_return_val_if_fail(item->folder != NULL, NULL);
1490
1491 folder = item->folder;
1492
1493 path = folder_item_get_path(item);
1494 g_return_val_if_fail(path != NULL, NULL);
1495 if (change_dir(path) < 0) {
1496 g_free(path);
1497 return NULL;
1498 }
1499 g_free(path);
1500
1501 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
1502 FILE_OP_ERROR(item->path, "opendir");
1503 return NULL;
1504 }
1505
1506 debug_print("Searching uncached messages...\n");
1507
1508 if (msg_table) {
1509 gint count = 0;
1510
1511 while ((dir_name = g_dir_read_name(dp)) != NULL) {
1512 if ((num = to_number(dir_name)) <= 0) continue;
1513
1514 msginfo = g_hash_table_lookup
1515 (msg_table, GUINT_TO_POINTER(num));
1516
1517 if (msginfo) {
1518 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_CACHED);
1519 } else {
1520 /* not found in the cache (uncached message) */
1521 msginfo = mh_parse_msg(dir_name, item);
1522 if (!msginfo) continue;
1523
1524 if (!newlist)
1525 last = newlist =
1526 g_slist_append(NULL, msginfo);
1527 else {
1528 last = g_slist_append(last, msginfo);
1529 last = last->next;
1530 }
1531 n_newmsg++;
1532 }
1533
1534 count++;
1535 if (folder->ui_func)
1536 folder->ui_func(folder, item, folder->ui_func_data ? folder->ui_func_data : GINT_TO_POINTER(count));
1537 }
1538 } else {
1539 /* discard all previous cache */
1540 while ((dir_name = g_dir_read_name(dp)) != NULL) {
1541 if (to_number(dir_name) <= 0) continue;
1542
1543 msginfo = mh_parse_msg(dir_name, item);
1544 if (!msginfo) continue;
1545
1546 if (!newlist)
1547 last = newlist = g_slist_append(NULL, msginfo);
1548 else {
1549 last = g_slist_append(last, msginfo);
1550 last = last->next;
1551 }
1552 n_newmsg++;
1553 if (folder->ui_func)
1554 folder->ui_func(folder, item, folder->ui_func_data ? folder->ui_func_data : GINT_TO_POINTER(n_newmsg));
1555 }
1556 }
1557
1558 g_dir_close(dp);
1559
1560 if (n_newmsg)
1561 debug_print("%d uncached message(s) found.\n", n_newmsg);
1562 else
1563 debug_print("done.\n");
1564
1565 /* sort new messages in numerical order */
1566 if (newlist && item->sort_key == SORT_BY_NONE) {
1567 debug_print("Sorting uncached messages in numerical order...\n");
1568 newlist = g_slist_sort
1569 (newlist, (GCompareFunc)procmsg_cmp_msgnum_for_sort);
1570 debug_print("done.\n");
1571 }
1572
1573 return newlist;
1574}
1575
1576static MsgInfo *mh_parse_msg(const gchar *file, FolderItem *item)
1577{
1578 MsgInfo *msginfo;
1579 MsgFlags flags;
1580
1581 g_return_val_if_fail(item != NULL, NULL);
1582 g_return_val_if_fail(file != NULL, NULL);
1583
1584 flags.perm_flags = MSG_NEW|MSG_UNREAD;
1585 flags.tmp_flags = 0;
1586
1587 if (item->stype == F_QUEUE) {
1588 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
1589 } else if (item->stype == F_DRAFT) {
1590 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
1591 }
1592
1593 msginfo = procheader_parse_file(file, flags, FALSE);
1594 if (!msginfo) return NULL;
1595
1596 msginfo->msgnum = atoi(file);
1597 msginfo->folder = item;
1598
1599 return msginfo;
1600}
1601
1602#if 0
1603static gboolean mh_is_maildir_one(const gchar *path, const gchar *dir)
1604{
1605 gchar *entry;
1606 gboolean result;
1607
1608 entry = g_strconcat(path, G_DIR_SEPARATOR_S, dir, NULL);
1609 result = is_dir_exist(entry);
1610 g_free(entry);
1611
1612 return result;
1613}
1614
1615/*
1616 * check whether PATH is a Maildir style mailbox.
1617 * This is the case if the 3 subdir: new, cur, tmp are existing.
1618 * This functon assumes that entry is an directory
1619 */
1620static gboolean mh_is_maildir(const gchar *path)
1621{
1622 return mh_is_maildir_one(path, "new") &&
1623 mh_is_maildir_one(path, "cur") &&
1624 mh_is_maildir_one(path, "tmp");
1625}
1626#endif
1627
1628static gboolean mh_remove_missing_folder_items_func(GNode *node, gpointer data)
1629{
1630 FolderItem *item;
1631 gchar *path;
1632
1633 g_return_val_if_fail(node->data != NULL, FALSE);
1634
1635 if (G_NODE_IS_ROOT(node))
1636 return FALSE;
1637
1638 item = FOLDER_ITEM(node->data);
1639
1640#if 0
1641 if (item->path && strchr(item->path, '/')) {
1642 debug_print("folder '%s' includes Unix path separator. removing...\n", item->path);
1643 folder_item_remove(item);
1644 return FALSE;
1645 }
1646#endif
1647
1648 path = folder_item_get_path(item);
1649 if (!is_dir_exist(path)) {
1650 debug_print("folder '%s' not found. removing...\n", path);
1651 folder_item_remove(item);
1652 }
1653 g_free(path);
1654
1655 return FALSE;
1656}
1657
1658static void mh_remove_missing_folder_items(Folder *folder)
1659{
1660 g_return_if_fail(folder != NULL);
1661
1662 debug_print("searching missing folders...\n");
1663
1664 g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
1665 mh_remove_missing_folder_items_func, folder);
1666}
1667
1668#define MAX_RECURSION_LEVEL 64
1669
1670static void mh_scan_tree_recursive(FolderItem *item)
1671{
1672 Folder *folder;
1673#ifdef G_OS_WIN32
1674 struct wfddata wfd;
1675 HANDLE hfind;
1676#else
1677 DIR *dp;
1678 struct dirent *d;
1679 GStatBuf s;
1680#endif
1681 const gchar *dir_name;
1682 gchar *fs_path;
1683 gchar *entry;
1684 gchar *utf8entry;
1685 gchar *utf8name;
1686 gint n_msg = 0;
1687
1688 g_return_if_fail(item != NULL);
1689 g_return_if_fail(item->folder != NULL);
1690
1691 if (item->stype == F_VIRTUAL)
1692 return;
1693
1694 folder = item->folder;
1695
1696 if (g_node_depth(item->node) >= MAX_RECURSION_LEVEL) {
1697 g_warning("mh_scan_tree_recursive(): max recursion level (%u) reached.", MAX_RECURSION_LEVEL);
1698 return;
1699 }
1700
1701 debug_print("scanning %s ...\n",
1702 item->path ? item->path : LOCAL_FOLDER(folder)->rootpath);
1703 if (folder->ui_func)
1704 folder->ui_func(folder, item, folder->ui_func_data);
1705
1706 fs_path = item->path ?
1707 g_filename_from_utf8(item->path, -1, NULL, NULL, NULL)
1708 : g_strdup(".");
1709 if (!fs_path)
1710 fs_path = g_strdup(item->path);
1711#ifdef G_OS_WIN32
1712 hfind = find_first_file(fs_path, &wfd);
1713 if (hfind == INVALID_HANDLE_VALUE) {
1714 g_warning("failed to open directory: %s\n", fs_path);
1715 g_free(fs_path);
1716 return;
1717 }
1718#else
1719 dp = opendir(fs_path);
1720 if (!dp) {
1721 FILE_OP_ERROR(fs_path, "opendir");
1722 g_free(fs_path);
1723 return;
1724 }
1725#endif
1726 g_free(fs_path);
1727
1728#ifdef G_OS_WIN32
1729 do {
1730 if (!wfd.file_name) continue;
1731 if (wfd.file_name[0] == '.') {
1732 g_free(wfd.file_name);
1733 wfd.file_name = NULL;
1734 continue;
1735 }
1736 dir_name = utf8name = wfd.file_name;
1737 wfd.file_name = NULL;
1738#else
1739 while ((d = readdir(dp)) != NULL) {
1740 dir_name = d->d_name;
1741 if (dir_name[0] == '.') continue;
1742
1743 utf8name = g_filename_to_utf8(dir_name, -1, NULL, NULL, NULL);
1744 if (!utf8name)
1745 utf8name = g_strdup(dir_name);
1746#endif
1747
1748 if (item->path) {
1749 utf8entry = g_strconcat(item->path, "/", utf8name,
1750 NULL);
1751 } else
1752 utf8entry = g_strdup(utf8name);
1753 entry = g_filename_from_utf8(utf8entry, -1, NULL, NULL, NULL);
1754 if (!entry)
1755 entry = g_strdup(utf8entry);
1756#ifdef G_OS_WIN32
1757 subst_char(entry, '/', G_DIR_SEPARATOR);
1758#endif
1759
1760 if (
1761#ifdef G_OS_WIN32
1762 (wfd.file_attr & FILE_ATTRIBUTE_DIRECTORY) != 0
1763#else
1764#if HAVE_DIRENT_D_TYPE
1765 d->d_type == DT_DIR ||
1766 ((d->d_type == DT_UNKNOWN || d->d_type == DT_LNK) &&
1767#endif
1768 g_stat(entry, &s) == 0 && S_ISDIR(s.st_mode)
1769#if HAVE_DIRENT_D_TYPE
1770 )
1771#endif
1772#endif /* G_OS_WIN32 */
1773 ) {
1774 FolderItem *new_item = NULL;
1775 GNode *node;
1776
1777#ifndef G_OS_WIN32
1778 if (g_utf8_validate(utf8name, -1, NULL) == FALSE) {
1779 g_warning(_("Directory name\n"
1780 "'%s' is not a valid UTF-8 string.\n"
1781 "Maybe the locale encoding is used for filename.\n"
1782 "If that is the case, you must set the following environmental variable\n"
1783 "(see README for detail):\n"
1784 "\n"
1785 "\tG_FILENAME_ENCODING=@locale\n"),
1786 utf8name);
1787 g_free(entry);
1788 g_free(utf8entry);
1789 g_free(utf8name);
1790 continue;
1791 }
1792#endif /* G_OS_WIN32 */
1793
1794 node = item->node;
1795 for (node = node->children; node != NULL; node = node->next) {
1796 FolderItem *cur_item = FOLDER_ITEM(node->data);
1797 if (!strcmp2(cur_item->path, utf8entry)) {
1798 new_item = cur_item;
1799 break;
1800 }
1801 }
1802 if (!new_item) {
1803 debug_print("new folder '%s' found.\n",
1804 utf8entry);
1805 new_item = folder_item_new(utf8name, utf8entry);
1806 folder_item_append(item, new_item);
1807 }
1808
1809 if (!item->path) {
1810 if (!folder->inbox &&
1811 !strcmp(dir_name, INBOX_DIR)) {
1812 new_item->stype = F_INBOX;
1813 folder->inbox = new_item;
1814 } else if (!folder->outbox &&
1815 !strcmp(dir_name, OUTBOX_DIR)) {
1816 new_item->stype = F_OUTBOX;
1817 folder->outbox = new_item;
1818 } else if (!folder->draft &&
1819 !strcmp(dir_name, DRAFT_DIR)) {
1820 new_item->stype = F_DRAFT;
1821 folder->draft = new_item;
1822 } else if (!folder->queue &&
1823 !strcmp(dir_name, QUEUE_DIR)) {
1824 new_item->stype = F_QUEUE;
1825 folder->queue = new_item;
1826 } else if (!folder->trash &&
1827 !strcmp(dir_name, TRASH_DIR)) {
1828 new_item->stype = F_TRASH;
1829 folder->trash = new_item;
1830 } else if (!folder_get_junk(folder) &&
1831 !strcmp(dir_name, JUNK_DIR)) {
1832 new_item->stype = F_JUNK;
1833 folder_set_junk(folder, new_item);
1834 }
1835 }
1836
1837 mh_scan_tree_recursive(new_item);
1838 } else if (to_number(dir_name) > 0) n_msg++;
1839
1840 g_free(entry);
1841 g_free(utf8entry);
1842 g_free(utf8name);
1843#ifdef G_OS_WIN32
1844 } while (find_next_file(hfind, &wfd));
1845#else
1846 }
1847#endif
1848
1849#ifdef G_OS_WIN32
1850 FindClose(hfind);
1851#else
1852 closedir(dp);
1853#endif
1854
1855 if (item->path) {
1856 gint new, unread, total, min, max;
1857
1858 procmsg_get_mark_sum
1859 (item, &new, &unread, &total, &min, &max, 0);
1860 if (n_msg > total) {
1861 new += n_msg - total;
1862 unread += n_msg - total;
1863 }
1864 item->new = new;
1865 item->unread = unread;
1866 item->total = n_msg;
1867 item->updated = TRUE;
1868 item->mtime = 0;
1869 }
1870}
1871
1872static gboolean mh_rename_folder_func(GNode *node, gpointer data)
1873{
1874 FolderItem *item = node->data;
1875 gchar **paths = data;
1876 const gchar *oldpath = paths[0];
1877 const gchar *newpath = paths[1];
1878 gchar *base;
1879 gchar *new_itempath;
1880 gint oldpathlen;
1881
1882 oldpathlen = strlen(oldpath);
1883 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
1884 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
1885 return TRUE;
1886 }
1887
1888 base = item->path + oldpathlen;
1889 while (*base == '/') base++;
1890 if (*base == '\0')
1891 new_itempath = g_strdup(newpath);
1892 else
1893 new_itempath = g_strconcat(newpath, "/", base, NULL);
1894 g_free(item->path);
1895 item->path = new_itempath;
1896
1897 return FALSE;
1898}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __MH_H__
21#define __MH_H__
22
23#include <glib.h>
24
25#include "folder.h"
26
27typedef struct _MHFolder MHFolder;
28
29#define MH_FOLDER(obj) ((MHFolder *)obj)
30
31struct _MHFolder
32{
33 LocalFolder lfolder;
34};
35
36FolderClass *mh_get_class (void);
37
38#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <stdio.h>
29#include <string.h>
30#include <stdlib.h>
31#include <dirent.h>
32#include <unistd.h>
33#include <time.h>
34
35#include "news.h"
36#include "nntp.h"
37#include "socket.h"
38#include "recv.h"
39#include "procmsg.h"
40#include "procheader.h"
41#include "folder.h"
42#include "session.h"
43#include "codeconv.h"
44#include "utils.h"
45#include "prefs_common.h"
46#include "prefs_account.h"
47#if USE_SSL
48# include "ssl.h"
49#endif
50#include "socks.h"
51
52#define NNTP_PORT 119
53#if USE_SSL
54#define NNTPS_PORT 563
55#endif
56
57static void news_folder_init (Folder *folder,
58 const gchar *name,
59 const gchar *path);
60
61static Folder *news_folder_new (const gchar *name,
62 const gchar *folder);
63static void news_folder_destroy (Folder *folder);
64
65static GSList *news_get_article_list (Folder *folder,
66 FolderItem *item,
67 gboolean use_cache);
68static gchar *news_fetch_msg (Folder *folder,
69 FolderItem *item,
70 gint num);
71static MsgInfo *news_get_msginfo (Folder *folder,
72 FolderItem *item,
73 gint num);
74
75static gint news_close (Folder *folder,
76 FolderItem *item);
77
78static gint news_scan_group (Folder *folder,
79 FolderItem *item);
80
81#if USE_SSL
82static Session *news_session_new (const gchar *server,
83 gushort port,
84 SocksInfo *socks_info,
85 const gchar *userid,
86 const gchar *passwd,
87 SSLType ssl_type);
88#else
89static Session *news_session_new (const gchar *server,
90 gushort port,
91 SocksInfo *socks_info,
92 const gchar *userid,
93 const gchar *passwd);
94#endif
95
96static gint news_get_article_cmd (NNTPSession *session,
97 const gchar *cmd,
98 gint num,
99 gchar *filename);
100static gint news_get_article (NNTPSession *session,
101 gint num,
102 gchar *filename);
103#if 0
104static gint news_get_header (NNTPSession *session,
105 gint num,
106 gchar *filename);
107#endif
108
109static gint news_select_group (NNTPSession *session,
110 const gchar *group,
111 gint *num,
112 gint *first,
113 gint *last);
114static GSList *news_get_uncached_articles(NNTPSession *session,
115 FolderItem *item,
116 gint cache_last,
117 gint *rfirst,
118 gint *rlast);
119static MsgInfo *news_parse_xover (const gchar *xover_str);
120static gchar *news_parse_xhdr (const gchar *xhdr_str,
121 MsgInfo *msginfo);
122static GSList *news_delete_old_articles (GSList *alist,
123 FolderItem *item,
124 gint first);
125static void news_delete_all_articles (FolderItem *item);
126static void news_delete_expired_caches (GSList *alist,
127 FolderItem *item);
128
129static FolderClass news_class =
130{
131 F_NEWS,
132
133 news_folder_new,
134 news_folder_destroy,
135
136 NULL,
137 NULL,
138
139 news_get_article_list,
140 NULL,
141 news_fetch_msg,
142 news_get_msginfo,
143 NULL,
144 NULL,
145 NULL,
146 NULL,
147 NULL,
148 NULL,
149 NULL,
150 NULL,
151 NULL,
152 NULL,
153 NULL,
154 NULL,
155 news_close,
156 news_scan_group,
157
158 NULL,
159 NULL,
160 NULL,
161 NULL
162};
163
164
165FolderClass *news_get_class(void)
166{
167 return &news_class;
168}
169
170static Folder *news_folder_new(const gchar *name, const gchar *path)
171{
172 Folder *folder;
173
174 folder = (Folder *)g_new0(NewsFolder, 1);
175 news_folder_init(folder, name, path);
176
177 return folder;
178}
179
180static void news_folder_destroy(Folder *folder)
181{
182 if (REMOTE_FOLDER(folder)->remove_cache_on_destroy) {
183 gchar *dir;
184 gchar *server;
185
186 dir = folder_get_path(folder);
187 if (is_dir_exist(dir))
188 remove_dir_recursive(dir);
189 g_free(dir);
190
191 server = uriencode_for_filename(folder->account->nntp_server);
192 dir = g_strconcat(get_news_cache_dir(), G_DIR_SEPARATOR_S,
193 server, NULL);
194 if (is_dir_exist(dir))
195 g_rmdir(dir);
196 g_free(dir);
197 g_free(server);
198 }
199
200 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
201}
202
203static void news_folder_init(Folder *folder, const gchar *name,
204 const gchar *path)
205{
206 folder->klass = news_get_class();
207 folder_remote_folder_init(folder, name, path);
208}
209
210#if USE_SSL
211static Session *news_session_new(const gchar *server, gushort port,
212 SocksInfo *socks_info,
213 const gchar *userid, const gchar *passwd,
214 SSLType ssl_type)
215#else
216static Session *news_session_new(const gchar *server, gushort port,
217 SocksInfo *socks_info,
218 const gchar *userid, const gchar *passwd)
219#endif
220{
221 gchar buf[NNTPBUFSIZE];
222 Session *session;
223
224 g_return_val_if_fail(server != NULL, NULL);
225
226 log_message(_("creating NNTP connection to %s:%d ...\n"), server, port);
227
228#if USE_SSL
229 session = nntp_session_new_full(server, port, socks_info, buf, userid, passwd, ssl_type);
230#else
231 session = nntp_session_new_full(server, port, socks_info, buf, userid, passwd);
232#endif
233
234 return session;
235}
236
237static Session *news_session_new_for_folder(Folder *folder)
238{
239 Session *session;
240 PrefsAccount *ac;
241 SocksInfo *socks_info = NULL;
242 const gchar *userid = NULL;
243 gchar *passwd = NULL;
244 gushort port;
245
246 g_return_val_if_fail(folder != NULL, NULL);
247 g_return_val_if_fail(folder->account != NULL, NULL);
248
249 ac = folder->account;
250 if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
251 userid = ac->userid;
252 if (ac->passwd && ac->passwd[0])
253 passwd = g_strdup(ac->passwd);
254 else
255 passwd = input_query_password(ac->nntp_server, userid);
256 }
257
258 if (ac->use_socks && ac->use_socks_for_recv && ac->proxy_host) {
259 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);
260 }
261
262#if USE_SSL
263 port = ac->set_nntpport ? ac->nntpport
264 : ac->ssl_nntp ? NNTPS_PORT : NNTP_PORT;
265 session = news_session_new(ac->nntp_server, port, socks_info,
266 userid, passwd, ac->ssl_nntp);
267#else
268 port = ac->set_nntpport ? ac->nntpport : NNTP_PORT;
269 session = news_session_new(ac->nntp_server, port, socks_info,
270 userid, passwd);
271#endif
272
273 if (socks_info)
274 socks_info_free(socks_info);
275
276 g_free(passwd);
277
278 return session;
279}
280
281static NNTPSession *news_session_get(Folder *folder)
282{
283 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
284
285 g_return_val_if_fail(folder != NULL, NULL);
286 g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL);
287 g_return_val_if_fail(folder->account != NULL, NULL);
288
289 if (!prefs_common.online_mode)
290 return NULL;
291
292 if (!rfolder->session) {
293 rfolder->session = news_session_new_for_folder(folder);
294 return NNTP_SESSION(rfolder->session);
295 }
296
297 if (time(NULL) - rfolder->session->last_access_time <
298 SESSION_TIMEOUT_INTERVAL) {
299 return NNTP_SESSION(rfolder->session);
300 }
301
302 if (nntp_mode(NNTP_SESSION(rfolder->session), FALSE)
303 != NN_SUCCESS) {
304 log_warning(_("NNTP connection to %s:%d has been"
305 " disconnected. Reconnecting...\n"),
306 folder->account->nntp_server,
307 folder->account->set_nntpport ?
308 folder->account->nntpport : NNTP_PORT);
309 session_destroy(rfolder->session);
310 rfolder->session = news_session_new_for_folder(folder);
311 }
312
313 if (rfolder->session)
314 session_set_access_time(rfolder->session);
315
316 return NNTP_SESSION(rfolder->session);
317}
318
319static GSList *news_get_article_list(Folder *folder, FolderItem *item,
320 gboolean use_cache)
321{
322 GSList *alist;
323 NNTPSession *session;
324
325 g_return_val_if_fail(folder != NULL, NULL);
326 g_return_val_if_fail(item != NULL, NULL);
327 g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL);
328
329 session = news_session_get(folder);
330
331 if (!session) {
332 alist = procmsg_read_cache(item, FALSE);
333 item->last_num = procmsg_get_last_num_in_msg_list(alist);
334 } else if (use_cache) {
335 GSList *newlist;
336 gint cache_last;
337 gint first, last;
338
339 alist = procmsg_read_cache(item, FALSE);
340
341 cache_last = procmsg_get_last_num_in_msg_list(alist);
342 newlist = news_get_uncached_articles
343 (session, item, cache_last, &first, &last);
344 if (newlist)
345 item->cache_dirty = TRUE;
346 if (first == 0 && last == 0) {
347 news_delete_all_articles(item);
348 procmsg_msg_list_free(alist);
349 alist = NULL;
350 item->cache_dirty = TRUE;
351 } else {
352 alist = news_delete_old_articles(alist, item, first);
353 news_delete_expired_caches(alist, item);
354 }
355
356 alist = g_slist_concat(alist, newlist);
357
358 item->last_num = last;
359 } else {
360 gint last;
361
362 alist = news_get_uncached_articles
363 (session, item, 0, NULL, &last);
364 news_delete_all_articles(item);
365 item->last_num = last;
366 item->cache_dirty = TRUE;
367 }
368
369 procmsg_set_flags(alist, item);
370
371 alist = procmsg_sort_msg_list(alist, item->sort_key, item->sort_type);
372
373 if (item->mark_queue)
374 item->mark_dirty = TRUE;
375
376 debug_print("cache_dirty: %d, mark_dirty: %d\n",
377 item->cache_dirty, item->mark_dirty);
378
379 if (!item->opened) {
380 if (item->cache_dirty)
381 procmsg_write_cache_list(item, alist);
382 if (item->mark_dirty)
383 procmsg_write_flags_list(item, alist);
384 }
385
386 return alist;
387}
388
389static gchar *news_fetch_msg(Folder *folder, FolderItem *item, gint num)
390{
391 gchar *path, *filename;
392 NNTPSession *session;
393 gchar nstr[16];
394 gint ok;
395
396 g_return_val_if_fail(folder != NULL, NULL);
397 g_return_val_if_fail(item != NULL, NULL);
398
399 path = folder_item_get_path(item);
400 if (!is_dir_exist(path))
401 make_dir_hier(path);
402 filename = g_strconcat(path, G_DIR_SEPARATOR_S, utos_buf(nstr, num),
403 NULL);
404 g_free(path);
405
406 if (is_file_exist(filename) && get_file_size(filename) > 0) {
407 debug_print(_("article %d has been already cached.\n"), num);
408 return filename;
409 }
410
411 session = news_session_get(folder);
412 if (!session) {
413 g_free(filename);
414 return NULL;
415 }
416
417 ok = news_select_group(session, item->path, NULL, NULL, NULL);
418 if (ok != NN_SUCCESS) {
419 if (ok == NN_SOCKET) {
420 session_destroy(SESSION(session));
421 REMOTE_FOLDER(folder)->session = NULL;
422 }
423 g_free(filename);
424 return NULL;
425 }
426
427 debug_print(_("getting article %d...\n"), num);
428 ok = news_get_article(NNTP_SESSION(REMOTE_FOLDER(folder)->session),
429 num, filename);
430 if (ok != NN_SUCCESS) {
431 g_warning(_("can't read article %d\n"), num);
432 if (ok == NN_SOCKET) {
433 session_destroy(SESSION(session));
434 REMOTE_FOLDER(folder)->session = NULL;
435 }
436 g_free(filename);
437 return NULL;
438 }
439
440 return filename;
441}
442
443static MsgInfo *news_get_msginfo(Folder *folder, FolderItem *item, gint num)
444{
445 MsgInfo *msginfo;
446 MsgFlags flags = {0, 0};
447 gchar *file;
448
449 g_return_val_if_fail(folder != NULL, NULL);
450 g_return_val_if_fail(item != NULL, NULL);
451
452 file = news_fetch_msg(folder, item, num);
453 if (!file) return NULL;
454
455 msginfo = procheader_parse_file(file, flags, FALSE);
456
457 g_free(file);
458
459 return msginfo;
460}
461
462static gint news_close(Folder *folder, FolderItem *item)
463{
464 return 0;
465}
466
467static gint news_scan_group(Folder *folder, FolderItem *item)
468{
469 NNTPSession *session;
470 gint num = 0, first = 0, last = 0;
471 gint new = 0, unread = 0, total = 0;
472 gint min = 0, max = 0;
473 gint ok;
474
475 g_return_val_if_fail(folder != NULL, -1);
476 g_return_val_if_fail(item != NULL, -1);
477
478 session = news_session_get(folder);
479 if (!session) return -1;
480
481 ok = news_select_group(session, item->path, &num, &first, &last);
482 if (ok != NN_SUCCESS) {
483 if (ok == NN_SOCKET) {
484 session_destroy(SESSION(session));
485 REMOTE_FOLDER(folder)->session = NULL;
486 }
487 return -1;
488 }
489
490 if (num == 0) {
491 item->new = item->unread = item->total = item->last_num = 0;
492 return 0;
493 }
494
495 procmsg_get_mark_sum(item, &new, &unread, &total, &min, &max, first);
496
497 if (max < first || last < min)
498 new = unread = total = num;
499 else {
500 if (min < first)
501 min = first;
502
503 if (last < max)
504 max = last;
505 else if (max < last) {
506 new += last - max;
507 unread += last - max;
508 }
509
510 if (new > num) new = num;
511 if (unread > num) unread = num;
512 }
513
514 item->new = new;
515 item->unread = unread;
516 item->total = num;
517 item->last_num = last;
518
519 return 0;
520}
521
522static NewsGroupInfo *news_group_info_new(const gchar *name,
523 gint first, gint last, gchar type)
524{
525 NewsGroupInfo *ginfo;
526
527 ginfo = g_new(NewsGroupInfo, 1);
528 ginfo->name = g_strdup(name);
529 ginfo->first = first;
530 ginfo->last = last;
531 ginfo->type = type;
532 ginfo->subscribed = FALSE;
533
534 return ginfo;
535}
536
537static void news_group_info_free(NewsGroupInfo *ginfo)
538{
539 g_free(ginfo->name);
540 g_free(ginfo);
541}
542
543static gint news_group_info_compare(NewsGroupInfo *ginfo1,
544 NewsGroupInfo *ginfo2)
545{
546 return g_ascii_strcasecmp(ginfo1->name, ginfo2->name);
547}
548
549GSList *news_get_group_list(Folder *folder)
550{
551 gchar *path, *filename;
552 FILE *fp;
553 GSList *list = NULL;
554 GSList *last = NULL;
555 gchar buf[NNTPBUFSIZE];
556
557 g_return_val_if_fail(folder != NULL, NULL);
558 g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL);
559
560 path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
561 if (!is_dir_exist(path))
562 make_dir_hier(path);
563 filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
564 g_free(path);
565
566 if ((fp = g_fopen(filename, "rb")) == NULL) {
567 NNTPSession *session;
568 gint ok;
569
570 session = news_session_get(folder);
571 if (!session) {
572 g_free(filename);
573 return NULL;
574 }
575
576 ok = nntp_list(session);
577 if (ok != NN_SUCCESS) {
578 if (ok == NN_SOCKET) {
579 session_destroy(SESSION(session));
580 REMOTE_FOLDER(folder)->session = NULL;
581 }
582 g_free(filename);
583 return NULL;
584 }
585 if (recv_write_to_file(SESSION(session)->sock, filename) < 0) {
586 log_warning("can't retrieve newsgroup list\n");
587 session_destroy(SESSION(session));
588 REMOTE_FOLDER(folder)->session = NULL;
589 g_free(filename);
590 return NULL;
591 }
592
593 if ((fp = g_fopen(filename, "rb")) == NULL) {
594 FILE_OP_ERROR(filename, "fopen");
595 g_free(filename);
596 return NULL;
597 }
598 }
599
600 while (fgets(buf, sizeof(buf), fp) != NULL) {
601 gchar *p = buf;
602 gchar *name;
603 gint last_num;
604 gint first_num;
605 gchar type;
606 NewsGroupInfo *ginfo;
607
608 p = strchr(p, ' ');
609 if (!p) {
610 strretchomp(buf);
611 log_warning("invalid LIST response: %s\n", buf);
612 continue;
613 }
614 *p = '\0';
615 p++;
616 name = buf;
617
618 if (sscanf(p, "%d %d %c", &last_num, &first_num, &type) < 3) {
619 strretchomp(p);
620 log_warning("invalid LIST response: %s %s\n", name, p);
621 continue;
622 }
623
624 ginfo = news_group_info_new(name, first_num, last_num, type);
625
626 if (!last)
627 last = list = g_slist_append(NULL, ginfo);
628 else {
629 last = g_slist_append(last, ginfo);
630 last = last->next;
631 }
632 }
633
634 fclose(fp);
635 g_free(filename);
636
637 list = g_slist_sort(list, (GCompareFunc)news_group_info_compare);
638
639 return list;
640}
641
642void news_group_list_free(GSList *group_list)
643{
644 GSList *cur;
645
646 if (!group_list) return;
647
648 for (cur = group_list; cur != NULL; cur = cur->next)
649 news_group_info_free((NewsGroupInfo *)cur->data);
650 g_slist_free(group_list);
651}
652
653void news_remove_group_list_cache(Folder *folder)
654{
655 gchar *path, *filename;
656
657 g_return_if_fail(folder != NULL);
658 g_return_if_fail(FOLDER_TYPE(folder) == F_NEWS);
659
660 path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
661 filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
662 g_free(path);
663
664 if (is_file_exist(filename)) {
665 if (remove(filename) < 0)
666 FILE_OP_ERROR(filename, "remove");
667 }
668 g_free(filename);
669}
670
671gint news_post(Folder *folder, const gchar *file)
672{
673 FILE *fp;
674 gint ok;
675
676 g_return_val_if_fail(folder != NULL, -1);
677 g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, -1);
678 g_return_val_if_fail(file != NULL, -1);
679
680 if ((fp = g_fopen(file, "rb")) == NULL) {
681 FILE_OP_ERROR(file, "fopen");
682 return -1;
683 }
684
685 ok = news_post_stream(folder, fp);
686
687 fclose(fp);
688
689 return ok;
690}
691
692gint news_post_stream(Folder *folder, FILE *fp)
693{
694 NNTPSession *session;
695 gint ok;
696
697 g_return_val_if_fail(folder != NULL, -1);
698 g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, -1);
699 g_return_val_if_fail(fp != NULL, -1);
700
701 session = news_session_get(folder);
702 if (!session) return -1;
703
704 ok = nntp_post(session, fp);
705 if (ok != NN_SUCCESS) {
706 log_warning(_("can't post article.\n"));
707 if (ok == NN_SOCKET) {
708 session_destroy(SESSION(session));
709 REMOTE_FOLDER(folder)->session = NULL;
710 }
711 return -1;
712 }
713
714 return 0;
715}
716
717static gint news_get_article_cmd(NNTPSession *session, const gchar *cmd,
718 gint num, gchar *filename)
719{
720 gchar *msgid;
721 gint ok;
722
723 ok = nntp_get_article(session, cmd, num, &msgid);
724 if (ok != NN_SUCCESS)
725 return ok;
726
727 debug_print("Message-Id = %s, num = %d\n", msgid, num);
728 g_free(msgid);
729
730 ok = recv_write_to_file(SESSION(session)->sock, filename);
731 if (ok < 0) {
732 log_warning(_("can't retrieve article %d\n"), num);
733 if (ok == -2)
734 return NN_SOCKET;
735 else
736 return NN_IOERR;
737 }
738
739 return NN_SUCCESS;
740}
741
742static gint news_get_article(NNTPSession *session, gint num, gchar *filename)
743{
744 return news_get_article_cmd(session, "ARTICLE", num, filename);
745}
746
747#if 0
748static gint news_get_header(NNTPSession *session, gint num, gchar *filename)
749{
750 return news_get_article_cmd(session, "HEAD", num, filename);
751}
752#endif
753
754/**
755 * news_select_group:
756 * @session: Active NNTP session.
757 * @group: Newsgroup name.
758 * @num: Estimated number of articles.
759 * @first: First article number.
760 * @last: Last article number.
761 *
762 * Select newsgroup @group with the GROUP command if it is not already
763 * selected in @session, or article numbers need to be returned.
764 *
765 * Return value: NNTP result code.
766 **/
767static gint news_select_group(NNTPSession *session, const gchar *group,
768 gint *num, gint *first, gint *last)
769{
770 gint ok;
771 gint num_, first_, last_;
772
773 if (!num || !first || !last) {
774 if (session->group &&
775 g_ascii_strcasecmp(session->group, group) == 0)
776 return NN_SUCCESS;
777 num = &num_;
778 first = &first_;
779 last = &last_;
780 }
781
782 g_free(session->group);
783 session->group = NULL;
784
785 ok = nntp_group(session, group, num, first, last);
786 if (ok == NN_SUCCESS)
787 session->group = g_strdup(group);
788 else
789 log_warning(_("can't select group: %s\n"), group);
790
791 return ok;
792}
793
794static GSList *news_get_uncached_articles(NNTPSession *session,
795 FolderItem *item, gint cache_last,
796 gint *rfirst, gint *rlast)
797{
798 gint ok;
799 gint num = 0, first = 0, last = 0, begin = 0, end = 0;
800 gchar buf[NNTPBUFSIZE];
801 GSList *newlist = NULL;
802 GSList *llast = NULL;
803 MsgInfo *msginfo;
804 gint max_articles;
805
806 if (rfirst) *rfirst = -1;
807 if (rlast) *rlast = -1;
808
809 g_return_val_if_fail(session != NULL, NULL);
810 g_return_val_if_fail(item != NULL, NULL);
811 g_return_val_if_fail(item->folder != NULL, NULL);
812 g_return_val_if_fail(item->folder->account != NULL, NULL);
813 g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_NEWS, NULL);
814
815 ok = news_select_group(session, item->path, &num, &first, &last);
816 if (ok != NN_SUCCESS) {
817 if (ok == NN_SOCKET) {
818 session_destroy(SESSION(session));
819 REMOTE_FOLDER(item->folder)->session = NULL;
820 }
821 return NULL;
822 }
823
824 /* calculate getting overview range */
825 if (first > last) {
826 log_warning(_("invalid article range: %d - %d\n"),
827 first, last);
828 return NULL;
829 }
830
831 if (rfirst) *rfirst = first;
832 if (rlast) *rlast = last;
833
834 if (cache_last < first)
835 begin = first;
836 else if (last < cache_last)
837 begin = first;
838 else if (last == cache_last) {
839 debug_print(_("no new articles.\n"));
840 return NULL;
841 } else
842 begin = cache_last + 1;
843 end = last;
844
845 max_articles = item->folder->account->max_nntp_articles;
846 if (max_articles > 0 && end - begin + 1 > max_articles)
847 begin = end - max_articles + 1;
848
849 log_message(_("getting xover %d - %d in %s...\n"),
850 begin, end, item->path);
851 ok = nntp_xover(session, begin, end);
852 if (ok != NN_SUCCESS) {
853 log_warning(_("can't get xover\n"));
854 if (ok == NN_SOCKET) {
855 session_destroy(SESSION(session));
856 REMOTE_FOLDER(item->folder)->session = NULL;
857 }
858 return NULL;
859 }
860
861 for (;;) {
862 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
863 log_warning(_("error occurred while getting xover.\n"));
864 session_destroy(SESSION(session));
865 REMOTE_FOLDER(item->folder)->session = NULL;
866 return newlist;
867 }
868
869 if (buf[0] == '.' && buf[1] == '\r') break;
870
871 msginfo = news_parse_xover(buf);
872 if (!msginfo) {
873 log_warning(_("invalid xover line: %s\n"), buf);
874 continue;
875 }
876
877 msginfo->folder = item;
878 msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
879 msginfo->flags.tmp_flags = MSG_NEWS;
880 msginfo->newsgroups = g_strdup(item->path);
881
882 if (!newlist)
883 llast = newlist = g_slist_append(newlist, msginfo);
884 else {
885 llast = g_slist_append(llast, msginfo);
886 llast = llast->next;
887 }
888 }
889
890 ok = nntp_xhdr(session, "to", begin, end);
891 if (ok != NN_SUCCESS) {
892 log_warning(_("can't get xhdr\n"));
893 if (ok == NN_SOCKET) {
894 session_destroy(SESSION(session));
895 REMOTE_FOLDER(item->folder)->session = NULL;
896 }
897 return newlist;
898 }
899
900 llast = newlist;
901
902 for (;;) {
903 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
904 log_warning(_("error occurred while getting xhdr.\n"));
905 session_destroy(SESSION(session));
906 REMOTE_FOLDER(item->folder)->session = NULL;
907 return newlist;
908 }
909
910 if (buf[0] == '.' && buf[1] == '\r') break;
911 if (!llast) {
912 g_warning("llast == NULL\n");
913 continue;
914 }
915
916 msginfo = (MsgInfo *)llast->data;
917 msginfo->to = news_parse_xhdr(buf, msginfo);
918
919 llast = llast->next;
920 }
921
922 ok = nntp_xhdr(session, "cc", begin, end);
923 if (ok != NN_SUCCESS) {
924 log_warning(_("can't get xhdr\n"));
925 if (ok == NN_SOCKET) {
926 session_destroy(SESSION(session));
927 REMOTE_FOLDER(item->folder)->session = NULL;
928 }
929 return newlist;
930 }
931
932 llast = newlist;
933
934 for (;;) {
935 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
936 log_warning(_("error occurred while getting xhdr.\n"));
937 session_destroy(SESSION(session));
938 REMOTE_FOLDER(item->folder)->session = NULL;
939 return newlist;
940 }
941
942 if (buf[0] == '.' && buf[1] == '\r') break;
943 if (!llast) {
944 g_warning("llast == NULL\n");
945 continue;
946 }
947
948 msginfo = (MsgInfo *)llast->data;
949 msginfo->cc = news_parse_xhdr(buf, msginfo);
950
951 llast = llast->next;
952 }
953
954 session_set_access_time(SESSION(session));
955
956 return newlist;
957}
958
959#define PARSE_ONE_PARAM(p, srcp) \
960{ \
961 p = strchr(srcp, '\t'); \
962 if (!p) return NULL; \
963 else \
964 *p++ = '\0'; \
965}
966
967static MsgInfo *news_parse_xover(const gchar *xover_str)
968{
969 MsgInfo *msginfo;
970 gchar *subject, *sender, *size, *line, *date, *msgid, *ref, *tmp;
971 gchar *p;
972 gint num, size_int, line_int;
973 gchar *xover_buf;
974
975 Xstrdup_a(xover_buf, xover_str, return NULL);
976
977 PARSE_ONE_PARAM(subject, xover_buf);
978 PARSE_ONE_PARAM(sender, subject);
979 PARSE_ONE_PARAM(date, sender);
980 PARSE_ONE_PARAM(msgid, date);
981 PARSE_ONE_PARAM(ref, msgid);
982 PARSE_ONE_PARAM(size, ref);
983 PARSE_ONE_PARAM(line, size);
984
985 tmp = strchr(line, '\t');
986 if (!tmp) tmp = strchr(line, '\r');
987 if (!tmp) tmp = strchr(line, '\n');
988 if (tmp) *tmp = '\0';
989
990 num = atoi(xover_str);
991 size_int = atoi(size);
992 line_int = atoi(line);
993
994 /* set MsgInfo */
995 msginfo = g_new0(MsgInfo, 1);
996 msginfo->msgnum = num;
997 msginfo->size = size_int;
998
999 msginfo->date = g_strdup(date);
1000 msginfo->date_t = procheader_date_parse(NULL, date, 0);
1001
1002 msginfo->from = conv_unmime_header(sender, NULL);
1003 msginfo->fromname = procheader_get_fromname(msginfo->from);
1004
1005 msginfo->subject = conv_unmime_header(subject, NULL);
1006
1007 extract_parenthesis(msgid, '<', '>');
1008 remove_space(msgid);
1009 if (*msgid != '\0')
1010 msginfo->msgid = g_strdup(msgid);
1011
1012 eliminate_parenthesis(ref, '(', ')');
1013 if ((p = strrchr(ref, '<')) != NULL) {
1014 extract_parenthesis(p, '<', '>');
1015 remove_space(p);
1016 if (*p != '\0')
1017 msginfo->inreplyto = g_strdup(p);
1018 }
1019
1020 return msginfo;
1021}
1022
1023static gchar *news_parse_xhdr(const gchar *xhdr_str, MsgInfo *msginfo)
1024{
1025 gchar *p;
1026 gchar *tmp;
1027 gint num;
1028
1029 p = strchr(xhdr_str, ' ');
1030 if (!p)
1031 return NULL;
1032 else
1033 p++;
1034
1035 num = atoi(xhdr_str);
1036 if (msginfo->msgnum != num) return NULL;
1037
1038 tmp = strchr(p, '\r');
1039 if (!tmp) tmp = strchr(p, '\n');
1040
1041 if (tmp)
1042 return g_strndup(p, tmp - p);
1043 else
1044 return g_strdup(p);
1045}
1046
1047static GSList *news_delete_old_articles(GSList *alist, FolderItem *item,
1048 gint first)
1049{
1050 GSList *cur, *next;
1051 MsgInfo *msginfo;
1052 gchar *dir;
1053
1054 g_return_val_if_fail(item != NULL, alist);
1055 g_return_val_if_fail(item->folder != NULL, alist);
1056 g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_NEWS, alist);
1057
1058 if (first < 2) return alist;
1059
1060 debug_print("Deleting cached articles 1 - %d ...\n", first - 1);
1061
1062 dir = folder_item_get_path(item);
1063 remove_numbered_files(dir, 1, first - 1);
1064 g_free(dir);
1065
1066 for (cur = alist; cur != NULL; ) {
1067 next = cur->next;
1068
1069 msginfo = (MsgInfo *)cur->data;
1070 if (msginfo && msginfo->msgnum < first) {
1071 procmsg_msginfo_free(msginfo);
1072 alist = g_slist_remove(alist, msginfo);
1073 item->cache_dirty = TRUE;
1074 }
1075
1076 cur = next;
1077 }
1078
1079 return alist;
1080}
1081
1082static void news_delete_all_articles(FolderItem *item)
1083{
1084 gchar *dir;
1085
1086 g_return_if_fail(item != NULL);
1087 g_return_if_fail(item->folder != NULL);
1088 g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS);
1089
1090 debug_print("Deleting all cached articles...\n");
1091
1092 dir = folder_item_get_path(item);
1093 remove_all_numbered_files(dir);
1094 g_free(dir);
1095}
1096
1097static void news_delete_expired_caches(GSList *alist, FolderItem *item)
1098{
1099 gchar *dir;
1100
1101 g_return_if_fail(item != NULL);
1102 g_return_if_fail(item->folder != NULL);
1103 g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS);
1104
1105 debug_print("Deleting expired cached articles...\n");
1106
1107 dir = folder_item_get_path(item);
1108 remove_expired_files(dir, 24 * 7);
1109 g_free(dir);
1110}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __NEWS_H__
21#define __NEWS_H__
22
23#include <glib.h>
24#include <stdio.h>
25
26#include "folder.h"
27
28typedef struct _NewsFolder NewsFolder;
29typedef struct _NewsGroupInfo NewsGroupInfo;
30
31#define NEWS_FOLDER(obj) ((NewsFolder *)obj)
32
33struct _NewsFolder
34{
35 RemoteFolder rfolder;
36
37 gboolean use_auth;
38};
39
40struct _NewsGroupInfo
41{
42 gchar *name;
43 guint first;
44 guint last;
45 gchar type;
46 gboolean subscribed;
47};
48
49FolderClass *news_get_class (void);
50
51GSList *news_get_group_list (Folder *folder);
52void news_group_list_free (GSList *group_list);
53void news_remove_group_list_cache (Folder *folder);
54
55gint news_post (Folder *folder,
56 const gchar *file);
57gint news_post_stream (Folder *folder,
58 FILE *fp);
59
60#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include <glib.h>
25#include <glib/gi18n.h>
26#include <stdio.h>
27#include <string.h>
28
29#include "nntp.h"
30#include "socket.h"
31#include "utils.h"
32#if USE_SSL
33# include "ssl.h"
34#endif
35#include "socks.h"
36
37static gint verbose = 1;
38
39static void nntp_session_destroy(Session *session);
40
41static gint nntp_ok (SockInfo *sock,
42 gchar *argbuf);
43
44static gint nntp_gen_send (SockInfo *sock,
45 const gchar *format,
46 ...);
47static gint nntp_gen_recv (SockInfo *sock,
48 gchar *buf,
49 gint size);
50static gint nntp_gen_command (NNTPSession *session,
51 gchar *argbuf,
52 const gchar *format,
53 ...);
54
55
56#if USE_SSL
57Session *nntp_session_new_full(const gchar *server, gushort port,
58 SocksInfo *socks_info, gchar *buf,
59 const gchar *userid, const gchar *passwd,
60 SSLType ssl_type)
61#else
62Session *nntp_session_new_full(const gchar *server, gushort port,
63 SocksInfo *socks_info, gchar *buf,
64 const gchar *userid, const gchar *passwd)
65#endif
66{
67 NNTPSession *session;
68 SockInfo *sock;
69 const gchar *server_;
70 gushort port_;
71
72 if (socks_info) {
73 server_ = socks_info->proxy_host;
74 port_ = socks_info->proxy_port;
75 } else {
76 server_ = server;
77 port_ = port;
78 }
79
80 if ((sock = sock_connect(server_, port_)) == NULL) {
81 log_warning(_("Can't connect to NNTP server: %s:%d\n"),
82 server, port);
83 return NULL;
84 }
85
86 if (socks_info) {
87 if (socks_connect(sock, server, port, socks_info) < 0) {
88 log_warning("Can't establish SOCKS connection: %s:%d\n",
89 server, port);
90 sock_close(sock);
91 return NULL;
92 }
93 }
94
95#if USE_SSL
96 if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
97 log_warning("Can't establish NNTP session with: %s:%d\n",
98 server, port);
99 sock_close(sock);
100 return NULL;
101 }
102#endif
103
104 if (nntp_ok(sock, buf) != NN_SUCCESS) {
105 sock_close(sock);
106 return NULL;
107 }
108
109 session = g_new0(NNTPSession, 1);
110
111 session_init(SESSION(session));
112
113 SESSION(session)->type = SESSION_NEWS;
114 SESSION(session)->server = g_strdup(server);
115 SESSION(session)->sock = sock;
116 SESSION(session)->last_access_time = time(NULL);
117 SESSION(session)->data = NULL;
118
119 SESSION(session)->destroy = nntp_session_destroy;
120
121 session->group = NULL;
122
123 if (userid && passwd) {
124 gint ok;
125
126 session->userid = g_strdup(userid);
127 session->passwd = g_strdup(passwd);
128
129 ok = nntp_gen_send(sock, "AUTHINFO USER %s", session->userid);
130 if (ok != NN_SUCCESS) {
131 session_destroy(SESSION(session));
132 return NULL;
133 }
134 ok = nntp_ok(sock, NULL);
135 if (ok == NN_AUTHCONT) {
136 ok = nntp_gen_send(sock, "AUTHINFO PASS %s",
137 session->passwd);
138 if (ok != NN_SUCCESS) {
139 session_destroy(SESSION(session));
140 return NULL;
141 }
142 ok = nntp_ok(sock, NULL);
143 if (ok != NN_SUCCESS)
144 session->auth_failed = TRUE;
145 }
146 if (ok == NN_SOCKET) {
147 session_destroy(SESSION(session));
148 return NULL;
149 }
150 }
151
152 session_set_access_time(SESSION(session));
153
154 return SESSION(session);
155}
156
157#if USE_SSL
158Session *nntp_session_new(const gchar *server, gushort port, gchar *buf,
159 const gchar *userid, const gchar *passwd,
160 SSLType ssl_type)
161{
162 return nntp_session_new_full(server, port, NULL, buf, userid, passwd,
163 ssl_type);
164}
165#else
166Session *nntp_session_new(const gchar *server, gushort port, gchar *buf,
167 const gchar *userid, const gchar *passwd)
168{
169 return nntp_session_new_full(server, port, NULL, buf, userid, passwd);
170}
171#endif
172
173static void nntp_session_destroy(Session *session)
174{
175 NNTPSession *nntp_session = NNTP_SESSION(session);
176
177 g_return_if_fail(session != NULL);
178
179 g_free(nntp_session->group);
180 g_free(nntp_session->userid);
181 g_free(nntp_session->passwd);
182}
183
184gint nntp_group(NNTPSession *session, const gchar *group,
185 gint *num, gint *first, gint *last)
186{
187 gint ok;
188 gint resp;
189 gchar buf[NNTPBUFSIZE];
190
191 ok = nntp_gen_command(session, buf, "GROUP %s", group);
192
193 if (ok != NN_SUCCESS && ok != NN_SOCKET && ok != NN_AUTHREQ) {
194 ok = nntp_mode(session, FALSE);
195 if (ok == NN_SUCCESS)
196 ok = nntp_gen_command(session, buf, "GROUP %s", group);
197 }
198
199 if (ok != NN_SUCCESS)
200 return ok;
201
202 if (sscanf(buf, "%d %d %d %d", &resp, num, first, last)
203 != 4) {
204 log_warning(_("protocol error: %s\n"), buf);
205 return NN_PROTOCOL;
206 }
207
208 return NN_SUCCESS;
209}
210
211gint nntp_get_article(NNTPSession *session, const gchar *cmd, gint num,
212 gchar **msgid)
213{
214 gint ok;
215 gchar buf[NNTPBUFSIZE];
216
217 if (num > 0)
218 ok = nntp_gen_command(session, buf, "%s %d", cmd, num);
219 else
220 ok = nntp_gen_command(session, buf, cmd);
221
222 if (ok != NN_SUCCESS)
223 return ok;
224
225 extract_parenthesis(buf, '<', '>');
226 if (buf[0] == '\0') {
227 log_warning(_("protocol error\n"));
228 *msgid = g_strdup("0");
229 } else
230 *msgid = g_strdup(buf);
231
232 return NN_SUCCESS;
233}
234
235gint nntp_article(NNTPSession *session, gint num, gchar **msgid)
236{
237 return nntp_get_article(session, "ARTICLE", num, msgid);
238}
239
240gint nntp_body(NNTPSession *session, gint num, gchar **msgid)
241{
242 return nntp_get_article(session, "BODY", num, msgid);
243}
244
245gint nntp_head(NNTPSession *session, gint num, gchar **msgid)
246{
247 return nntp_get_article(session, "HEAD", num, msgid);
248}
249
250gint nntp_stat(NNTPSession *session, gint num, gchar **msgid)
251{
252 return nntp_get_article(session, "STAT", num, msgid);
253}
254
255gint nntp_next(NNTPSession *session, gint *num, gchar **msgid)
256{
257 gint ok;
258 gint resp;
259 gchar buf[NNTPBUFSIZE];
260
261 ok = nntp_gen_command(session, buf, "NEXT");
262
263 if (ok != NN_SUCCESS)
264 return ok;
265
266 if (sscanf(buf, "%d %d", &resp, num) != 2) {
267 log_warning(_("protocol error: %s\n"), buf);
268 return NN_PROTOCOL;
269 }
270
271 extract_parenthesis(buf, '<', '>');
272 if (buf[0] == '\0') {
273 log_warning(_("protocol error\n"));
274 return NN_PROTOCOL;
275 }
276 *msgid = g_strdup(buf);
277
278 return NN_SUCCESS;
279}
280
281gint nntp_xover(NNTPSession *session, gint first, gint last)
282{
283 gint ok;
284 gchar buf[NNTPBUFSIZE];
285
286 ok = nntp_gen_command(session, buf, "XOVER %d-%d", first, last);
287 if (ok != NN_SUCCESS)
288 return ok;
289
290 return NN_SUCCESS;
291}
292
293gint nntp_xhdr(NNTPSession *session, const gchar *header, gint first, gint last)
294{
295 gint ok;
296 gchar buf[NNTPBUFSIZE];
297
298 ok = nntp_gen_command(session, buf, "XHDR %s %d-%d",
299 header, first, last);
300 if (ok != NN_SUCCESS)
301 return ok;
302
303 return NN_SUCCESS;
304}
305
306gint nntp_list(NNTPSession *session)
307{
308 return nntp_gen_command(session, NULL, "LIST");
309}
310
311gint nntp_post(NNTPSession *session, FILE *fp)
312{
313 gint ok;
314 gchar buf[NNTPBUFSIZE];
315 gchar *msg;
316
317 ok = nntp_gen_command(session, buf, "POST");
318 if (ok != NN_SUCCESS)
319 return ok;
320
321 msg = get_outgoing_rfc2822_str(fp);
322 if (sock_write_all(SESSION(session)->sock, msg, strlen(msg)) < 0) {
323 log_warning(_("Error occurred while posting\n"));
324 g_free(msg);
325 return NN_SOCKET;
326 }
327 g_free(msg);
328
329 sock_write_all(SESSION(session)->sock, ".\r\n", 3);
330 if ((ok = nntp_ok(SESSION(session)->sock, buf)) != NN_SUCCESS)
331 return ok;
332
333 session_set_access_time(SESSION(session));
334
335 return NN_SUCCESS;
336}
337
338gint nntp_newgroups(NNTPSession *session)
339{
340 return NN_SUCCESS;
341}
342
343gint nntp_newnews(NNTPSession *session)
344{
345 return NN_SUCCESS;
346}
347
348gint nntp_mode(NNTPSession *session, gboolean stream)
349{
350 gint ok;
351
352 ok = nntp_gen_command(session, NULL, "MODE %s",
353 stream ? "STREAM" : "READER");
354
355 return ok;
356}
357
358static gint nntp_ok(SockInfo *sock, gchar *argbuf)
359{
360 gint ok;
361 gchar buf[NNTPBUFSIZE];
362
363 if ((ok = nntp_gen_recv(sock, buf, sizeof(buf))) == NN_SUCCESS) {
364 if (strlen(buf) < 3)
365 return NN_ERROR;
366
367 if ((buf[0] == '1' || buf[0] == '2' || buf[0] == '3') &&
368 (buf[3] == ' ' || buf[3] == '\0')) {
369 if (argbuf)
370 strcpy(argbuf, buf);
371
372 if (!strncmp(buf, "381", 3))
373 return NN_AUTHCONT;
374
375 return NN_SUCCESS;
376 } else if (!strncmp(buf, "480", 3))
377 return NN_AUTHREQ;
378 else
379 return NN_ERROR;
380 }
381
382 return ok;
383}
384
385static gint nntp_gen_send(SockInfo *sock, const gchar *format, ...)
386{
387 gchar buf[NNTPBUFSIZE];
388 va_list args;
389
390 va_start(args, format);
391 g_vsnprintf(buf, sizeof(buf), format, args);
392 va_end(args);
393
394 if (verbose) {
395 if (!g_ascii_strncasecmp(buf, "AUTHINFO PASS", 13))
396 log_print("NNTP> AUTHINFO PASS ********\n");
397 else
398 log_print("NNTP> %s\n", buf);
399 }
400
401 strcat(buf, "\r\n");
402 if (sock_write_all(sock, buf, strlen(buf)) < 0) {
403 log_warning(_("Error occurred while sending command\n"));
404 return NN_SOCKET;
405 }
406
407 return NN_SUCCESS;
408}
409
410static gint nntp_gen_recv(SockInfo *sock, gchar *buf, gint size)
411{
412 if (sock_gets(sock, buf, size) == -1)
413 return NN_SOCKET;
414
415 strretchomp(buf);
416
417 if (verbose)
418 log_print("NNTP< %s\n", buf);
419
420 return NN_SUCCESS;
421}
422
423static gint nntp_gen_command(NNTPSession *session, gchar *argbuf,
424 const gchar *format, ...)
425{
426 gchar buf[NNTPBUFSIZE];
427 va_list args;
428 gint ok;
429 SockInfo *sock;
430
431 va_start(args, format);
432 g_vsnprintf(buf, sizeof(buf), format, args);
433 va_end(args);
434
435 sock = SESSION(session)->sock;
436 ok = nntp_gen_send(sock, "%s", buf);
437 if (ok != NN_SUCCESS)
438 return ok;
439 ok = nntp_ok(sock, argbuf);
440 if (ok == NN_AUTHREQ) {
441 if (!session->userid || !session->passwd) {
442 session->auth_failed = TRUE;
443 return ok;
444 }
445
446 ok = nntp_gen_send(sock, "AUTHINFO USER %s", session->userid);
447 if (ok != NN_SUCCESS)
448 return ok;
449 ok = nntp_ok(sock, NULL);
450 if (ok == NN_AUTHCONT) {
451 ok = nntp_gen_send(sock, "AUTHINFO PASS %s",
452 session->passwd);
453 if (ok != NN_SUCCESS)
454 return ok;
455 ok = nntp_ok(sock, NULL);
456 }
457 if (ok != NN_SUCCESS) {
458 session->auth_failed = TRUE;
459 return ok;
460 }
461
462 ok = nntp_gen_send(sock, "%s", buf);
463 if (ok != NN_SUCCESS)
464 return ok;
465 ok = nntp_ok(sock, argbuf);
466 }
467
468 session_set_access_time(SESSION(session));
469
470 return ok;
471}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __NNTP_H__
21#define __NNTP_H__
22
23#include "session.h"
24#if USE_SSL
25# include "ssl.h"
26#endif
27#include "socks.h"
28
29typedef struct _NNTPSession NNTPSession;
30
31#define NNTP_SESSION(obj) ((NNTPSession *)obj)
32
33struct _NNTPSession
34{
35 Session session;
36
37 gchar *group;
38
39 gchar *userid;
40 gchar *passwd;
41 gboolean auth_failed;
42};
43
44#define NN_SUCCESS 0
45#define NN_SOCKET 2
46#define NN_AUTHFAIL 3
47#define NN_PROTOCOL 4
48#define NN_SYNTAX 5
49#define NN_IOERR 6
50#define NN_ERROR 7
51#define NN_AUTHREQ 8
52#define NN_AUTHCONT 9
53
54#define NNTPBUFSIZE 8192
55
56#if USE_SSL
57Session *nntp_session_new (const gchar *server,
58 gushort port,
59 gchar *buf,
60 const gchar *userid,
61 const gchar *passwd,
62 SSLType ssl_type);
63Session *nntp_session_new_full (const gchar *server,
64 gushort port,
65 SocksInfo *socks_info,
66 gchar *buf,
67 const gchar *userid,
68 const gchar *passwd,
69 SSLType ssl_type);
70#else
71Session *nntp_session_new (const gchar *server,
72 gushort port,
73 gchar *buf,
74 const gchar *userid,
75 const gchar *passwd);
76Session *nntp_session_new_full (const gchar *server,
77 gushort port,
78 SocksInfo *socks_info,
79 gchar *buf,
80 const gchar *userid,
81 const gchar *passwd);
82#endif
83
84gint nntp_group (NNTPSession *session,
85 const gchar *group,
86 gint *num,
87 gint *first,
88 gint *last);
89gint nntp_get_article (NNTPSession *session,
90 const gchar *cmd,
91 gint num,
92 gchar **msgid);
93gint nntp_article (NNTPSession *session,
94 gint num,
95 gchar **msgid);
96gint nntp_body (NNTPSession *session,
97 gint num,
98 gchar **msgid);
99gint nntp_head (NNTPSession *session,
100 gint num,
101 gchar **msgid);
102gint nntp_stat (NNTPSession *session,
103 gint num,
104 gchar **msgid);
105gint nntp_next (NNTPSession *session,
106 gint *num,
107 gchar **msgid);
108gint nntp_xover (NNTPSession *session,
109 gint first,
110 gint last);
111gint nntp_xhdr (NNTPSession *session,
112 const gchar *header,
113 gint first,
114 gint last);
115gint nntp_list (NNTPSession *session);
116gint nntp_post (NNTPSession *session,
117 FILE *fp);
118gint nntp_newgroups (NNTPSession *session);
119gint nntp_newnews (NNTPSession *session);
120gint nntp_mode (NNTPSession *session,
121 gboolean stream);
122
123#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2014 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <stdio.h>
29#include <string.h>
30#include <stdarg.h>
31#include <ctype.h>
32#include <unistd.h>
33#include <time.h>
34#include <errno.h>
35
36#include "pop.h"
37#include "md5.h"
38#include "prefs.h"
39#include "prefs_account.h"
40#include "utils.h"
41#include "recv.h"
42
43gint pop3_greeting_recv (Pop3Session *session,
44 const gchar *msg);
45gint pop3_getauth_user_send (Pop3Session *session);
46gint pop3_getauth_pass_send (Pop3Session *session);
47gint pop3_getauth_apop_send (Pop3Session *session);
48#if USE_SSL
49gint pop3_stls_send (Pop3Session *session);
50gint pop3_stls_recv (Pop3Session *session);
51#endif
52gint pop3_getrange_stat_send (Pop3Session *session);
53gint pop3_getrange_stat_recv (Pop3Session *session,
54 const gchar *msg);
55gint pop3_getrange_last_send (Pop3Session *session);
56gint pop3_getrange_last_recv (Pop3Session *session,
57 const gchar *msg);
58gint pop3_getrange_uidl_send (Pop3Session *session);
59gint pop3_getrange_uidl_recv (Pop3Session *session,
60 const gchar *data,
61 guint len);
62gint pop3_getsize_list_send (Pop3Session *session);
63gint pop3_getsize_list_recv (Pop3Session *session,
64 const gchar *data,
65 guint len);
66gint pop3_retr_send (Pop3Session *session);
67gint pop3_retr_recv (Pop3Session *session,
68 FILE *fp,
69 guint len);
70gint pop3_delete_send (Pop3Session *session);
71gint pop3_delete_recv (Pop3Session *session);
72gint pop3_logout_send (Pop3Session *session);
73
74void pop3_gen_send (Pop3Session *session,
75 const gchar *format, ...);
76
77static void pop3_session_destroy (Session *session);
78
79gint pop3_write_msg_to_file (const gchar *file,
80 FILE *src_fp,
81 guint len);
82
83static Pop3State pop3_lookup_next (Pop3Session *session);
84
85Pop3ErrorValue pop3_ok (Pop3Session *session,
86 const gchar *msg);
87
88static gint pop3_session_recv_msg (Session *session,
89 const gchar *msg);
90static gint pop3_session_recv_data_finished (Session *session,
91 guchar *data,
92 guint len);
93static gint pop3_session_recv_data_as_file_finished
94 (Session *session,
95 FILE *fp,
96 guint len);
97
98
99gint pop3_greeting_recv(Pop3Session *session, const gchar *msg)
100{
101 session->state = POP3_GREETING;
102
103 session->greeting = g_strdup(msg);
104 return PS_SUCCESS;
105}
106
107#if USE_SSL
108gint pop3_stls_send(Pop3Session *session)
109{
110 session->state = POP3_STLS;
111 pop3_gen_send(session, "STLS");
112 return PS_SUCCESS;
113}
114
115gint pop3_stls_recv(Pop3Session *session)
116{
117 if (session_start_tls(SESSION(session)) < 0) {
118 session->error_val = PS_SOCKET;
119 return PS_SOCKET;
120 }
121 return PS_SUCCESS;
122}
123#endif /* USE_SSL */
124
125gint pop3_getauth_user_send(Pop3Session *session)
126{
127 g_return_val_if_fail(session->user != NULL, -1);
128
129 session->state = POP3_GETAUTH_USER;
130 pop3_gen_send(session, "USER %s", session->user);
131 return PS_SUCCESS;
132}
133
134gint pop3_getauth_pass_send(Pop3Session *session)
135{
136 g_return_val_if_fail(session->pass != NULL, -1);
137
138 session->state = POP3_GETAUTH_PASS;
139 pop3_gen_send(session, "PASS %s", session->pass);
140 return PS_SUCCESS;
141}
142
143gint pop3_getauth_apop_send(Pop3Session *session)
144{
145 gchar *start, *end;
146 gchar *apop_str;
147 SMD5 *md5;
148 gchar *md5sum;
149
150 g_return_val_if_fail(session->user != NULL, -1);
151 g_return_val_if_fail(session->pass != NULL, -1);
152
153 session->state = POP3_GETAUTH_APOP;
154
155 if ((start = strchr(session->greeting, '<')) == NULL) {
156 log_warning(_("Required APOP timestamp not found "
157 "in greeting\n"));
158 session->error_val = PS_PROTOCOL;
159 return PS_PROTOCOL;
160 }
161
162 if ((end = strchr(start, '>')) == NULL || end == start + 1) {
163 log_warning(_("Timestamp syntax error in greeting\n"));
164 session->error_val = PS_PROTOCOL;
165 return PS_PROTOCOL;
166 }
167
168 *(end + 1) = '\0';
169
170 if (!is_ascii_str(start) || strchr(start, '@') == NULL) {
171 log_warning(_("Invalid timestamp in greeting\n"));
172 session->error_val = PS_PROTOCOL;
173 return PS_PROTOCOL;
174 }
175
176 apop_str = g_strconcat(start, session->pass, NULL);
177 md5 = s_gnet_md5_new((guchar *)apop_str, strlen(apop_str));
178 md5sum = s_gnet_md5_get_string(md5);
179
180 pop3_gen_send(session, "APOP %s %s", session->user, md5sum);
181
182 g_free(md5sum);
183 s_gnet_md5_delete(md5);
184 g_free(apop_str);
185
186 return PS_SUCCESS;
187}
188
189gint pop3_getrange_stat_send(Pop3Session *session)
190{
191 session->state = POP3_GETRANGE_STAT;
192 pop3_gen_send(session, "STAT");
193 return PS_SUCCESS;
194}
195
196gint pop3_getrange_stat_recv(Pop3Session *session, const gchar *msg)
197{
198 if (sscanf(msg, "%d %lld", &session->count, &session->total_bytes) != 2) {
199 log_warning(_("POP3 protocol error\n"));
200 session->error_val = PS_PROTOCOL;
201 return PS_PROTOCOL;
202 } else {
203 if (session->count == 0) {
204 session->uidl_is_valid = TRUE;
205 } else {
206 session->msg = g_new0(Pop3MsgInfo, session->count + 1);
207 session->cur_msg = 1;
208 }
209 }
210
211 return PS_SUCCESS;
212}
213
214gint pop3_getrange_last_send(Pop3Session *session)
215{
216 session->state = POP3_GETRANGE_LAST;
217 pop3_gen_send(session, "LAST");
218 return PS_SUCCESS;
219}
220
221gint pop3_getrange_last_recv(Pop3Session *session, const gchar *msg)
222{
223 gint last;
224
225 if (sscanf(msg, "%d", &last) == 0) {
226 log_warning(_("POP3 protocol error\n"));
227 session->error_val = PS_PROTOCOL;
228 return PS_PROTOCOL;
229 } else {
230 if (session->count > last) {
231 session->new_msg_exist = TRUE;
232 session->cur_msg = last + 1;
233 } else
234 session->cur_msg = 0;
235 }
236
237 return PS_SUCCESS;
238}
239
240gint pop3_getrange_uidl_send(Pop3Session *session)
241{
242 session->state = POP3_GETRANGE_UIDL;
243 pop3_gen_send(session, "UIDL");
244 return PS_SUCCESS;
245}
246
247gint pop3_getrange_uidl_recv(Pop3Session *session, const gchar *data, guint len)
248{
249 gchar id[IDLEN + 1];
250 gchar buf[POPBUFSIZE];
251 gint buf_len;
252 gint num;
253 time_t recv_time;
254 const gchar *p = data;
255 const gchar *lastp = data + len;
256 const gchar *newline;
257
258 while (p < lastp) {
259 if ((newline = memchr(p, '\r', lastp - p)) == NULL)
260 return PS_PROTOCOL;
261 buf_len = MIN(newline - p, sizeof(buf) - 1);
262 memcpy(buf, p, buf_len);
263 buf[buf_len] = '\0';
264
265 p = newline + 1;
266 if (p < lastp && *p == '\n') p++;
267
268 if (sscanf(buf, "%d %" Xstr(IDLEN) "s", &num, id) != 2 ||
269 num <= 0 || num > session->count) {
270 log_warning(_("invalid UIDL response: %s\n"), buf);
271 continue;
272 }
273
274 session->msg[num].uidl = g_strdup(id);
275
276 recv_time = GPOINTER_TO_INT(g_hash_table_lookup(session->uidl_table, id));
277 session->msg[num].recv_time = recv_time;
278
279 if (!session->ac_prefs->getall && recv_time != RECV_TIME_NONE)
280 session->msg[num].received = TRUE;
281
282 if (!session->new_msg_exist &&
283 (session->ac_prefs->getall || recv_time == RECV_TIME_NONE ||
284 session->ac_prefs->rmmail)) {
285 session->cur_msg = num;
286 session->new_msg_exist = TRUE;
287 }
288 }
289
290 session->uidl_is_valid = TRUE;
291 return PS_SUCCESS;
292}
293
294gint pop3_getsize_list_send(Pop3Session *session)
295{
296 session->state = POP3_GETSIZE_LIST;
297 pop3_gen_send(session, "LIST");
298 return PS_SUCCESS;
299}
300
301gint pop3_getsize_list_recv(Pop3Session *session, const gchar *data, guint len)
302{
303 gchar buf[POPBUFSIZE];
304 gint buf_len;
305 guint num, size;
306 const gchar *p = data;
307 const gchar *lastp = data + len;
308 const gchar *newline;
309
310 while (p < lastp) {
311 if ((newline = memchr(p, '\r', lastp - p)) == NULL)
312 return PS_PROTOCOL;
313 buf_len = MIN(newline - p, sizeof(buf) - 1);
314 memcpy(buf, p, buf_len);
315 buf[buf_len] = '\0';
316
317 p = newline + 1;
318 if (p < lastp && *p == '\n') p++;
319
320 if (sscanf(buf, "%u %u", &num, &size) != 2) {
321 session->error_val = PS_PROTOCOL;
322 return PS_PROTOCOL;
323 }
324
325 if (num > 0 && num <= session->count)
326 session->msg[num].size = size;
327 if (num > 0 && num < session->cur_msg)
328 session->cur_total_bytes += size;
329 }
330
331 return PS_SUCCESS;
332}
333
334gint pop3_retr_send(Pop3Session *session)
335{
336 session->state = POP3_RETR;
337 pop3_gen_send(session, "RETR %d", session->cur_msg);
338 return PS_SUCCESS;
339}
340
341gint pop3_retr_recv(Pop3Session *session, FILE *fp, guint len)
342{
343 gchar *file;
344 gint drop_ok;
345
346 file = get_tmp_file();
347 if (pop3_write_msg_to_file(file, fp, len) < 0) {
348 g_free(file);
349 session->error_val = PS_IOERR;
350 return PS_IOERR;
351 }
352
353 drop_ok = session->drop_message(session, file);
354 g_unlink(file);
355 g_free(file);
356 if (drop_ok < 0) {
357 session->error_val = PS_IOERR;
358 return PS_IOERR;
359 }
360
361 session->cur_total_bytes += session->msg[session->cur_msg].size;
362 session->cur_total_recv_bytes += session->msg[session->cur_msg].size;
363 session->cur_total_num++;
364
365 session->msg[session->cur_msg].received = TRUE;
366 session->msg[session->cur_msg].recv_time =
367 drop_ok == DROP_DONT_RECEIVE ? RECV_TIME_KEEP
368 : drop_ok == DROP_DELETE ? RECV_TIME_DELETE
369 : session->current_time;
370
371 return PS_SUCCESS;
372}
373
374gint pop3_delete_send(Pop3Session *session)
375{
376 session->state = POP3_DELETE;
377 pop3_gen_send(session, "DELE %d", session->cur_msg);
378 return PS_SUCCESS;
379}
380
381gint pop3_delete_recv(Pop3Session *session)
382{
383 session->msg[session->cur_msg].recv_time = RECV_TIME_DELETE;
384 session->msg[session->cur_msg].deleted = TRUE;
385 return PS_SUCCESS;
386}
387
388gint pop3_logout_send(Pop3Session *session)
389{
390 session->state = POP3_LOGOUT;
391 pop3_gen_send(session, "QUIT");
392 return PS_SUCCESS;
393}
394
395void pop3_gen_send(Pop3Session *session, const gchar *format, ...)
396{
397 gchar buf[POPBUFSIZE + 1];
398 va_list args;
399
400 va_start(args, format);
401 g_vsnprintf(buf, sizeof(buf) - 2, format, args);
402 va_end(args);
403
404 if (!g_ascii_strncasecmp(buf, "PASS ", 5))
405 log_print("POP3> PASS ********\n");
406 else
407 log_print("POP3> %s\n", buf);
408
409 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf);
410}
411
412Session *pop3_session_new(PrefsAccount *account)
413{
414 Pop3Session *session;
415
416 g_return_val_if_fail(account != NULL, NULL);
417
418 session = g_new0(Pop3Session, 1);
419
420 session_init(SESSION(session));
421
422 SESSION(session)->type = SESSION_POP3;
423
424 SESSION(session)->recv_msg = pop3_session_recv_msg;
425 SESSION(session)->send_data_finished = NULL;
426 SESSION(session)->recv_data_finished = pop3_session_recv_data_finished;
427 SESSION(session)->recv_data_as_file_finished =
428 pop3_session_recv_data_as_file_finished;
429
430 SESSION(session)->destroy = pop3_session_destroy;
431
432 session->state = POP3_READY;
433 session->ac_prefs = account;
434 session->uidl_table = pop3_get_uidl_table(account);
435 session->current_time = time(NULL);
436 session->error_val = PS_SUCCESS;
437 session->error_msg = NULL;
438
439 session->user = g_strdup(account->userid);
440 session->pass = account->passwd ? g_strdup(account->passwd) :
441 account->tmp_pass ? g_strdup(account->tmp_pass) : NULL;
442
443 SESSION(session)->server = g_strdup(account->recv_server);
444
445#if USE_SSL
446 SESSION(session)->port = account->set_popport ?
447 account->popport : account->ssl_pop == SSL_TUNNEL ? 995 : 110;
448 SESSION(session)->ssl_type = account->ssl_pop;
449 if (account->ssl_pop != SSL_NONE)
450 SESSION(session)->nonblocking = account->use_nonblocking_ssl;
451#else
452 SESSION(session)->port = account->set_popport ? account->popport : 110;
453#endif
454
455 return SESSION(session);
456}
457
458static void pop3_session_destroy(Session *session)
459{
460 Pop3Session *pop3_session = POP3_SESSION(session);
461 gint n;
462
463 g_return_if_fail(session != NULL);
464
465 for (n = 1; n <= pop3_session->count; n++)
466 g_free(pop3_session->msg[n].uidl);
467 g_free(pop3_session->msg);
468
469 if (pop3_session->uidl_table) {
470 hash_free_strings(pop3_session->uidl_table);
471 g_hash_table_destroy(pop3_session->uidl_table);
472 }
473
474 g_free(pop3_session->greeting);
475 g_free(pop3_session->user);
476 g_free(pop3_session->pass);
477 g_free(pop3_session->error_msg);
478}
479
480GHashTable *pop3_get_uidl_table(PrefsAccount *ac_prefs)
481{
482 GHashTable *table;
483 gchar *path;
484 FILE *fp;
485 gchar buf[POPBUFSIZE];
486 gchar uidl[POPBUFSIZE];
487 time_t recv_time;
488 time_t now;
489 gchar *uid;
490
491 table = g_hash_table_new(g_str_hash, g_str_equal);
492
493 uid = uriencode_for_filename(ac_prefs->userid);
494 path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
495 UIDL_DIR, G_DIR_SEPARATOR_S, ac_prefs->recv_server,
496 "-", uid, NULL);
497 g_free(uid);
498 if ((fp = g_fopen(path, "rb")) == NULL) {
499 if (ENOENT != errno) FILE_OP_ERROR(path, "fopen");
500 g_free(path);
501 return table;
502 }
503 g_free(path);
504
505 now = time(NULL);
506
507 while (fgets(buf, sizeof(buf), fp) != NULL) {
508 strretchomp(buf);
509 recv_time = RECV_TIME_NONE;
510 if (sscanf(buf, "%s\t%ld", uidl, &recv_time) != 2) {
511 if (sscanf(buf, "%s", uidl) != 1)
512 continue;
513 else
514 recv_time = now;
515 }
516 if (recv_time == RECV_TIME_NONE)
517 recv_time = RECV_TIME_RECEIVED;
518 g_hash_table_insert(table, g_strdup(uidl),
519 GINT_TO_POINTER(recv_time));
520 }
521
522 fclose(fp);
523 return table;
524}
525
526gint pop3_write_uidl_list(Pop3Session *session)
527{
528 gchar *path;
529 PrefFile *pfile;
530 Pop3MsgInfo *msg;
531 gint n;
532 gchar *uid;
533
534 if (!session->uidl_is_valid) return 0;
535
536 uid = uriencode_for_filename(session->ac_prefs->userid);
537 path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
538 UIDL_DIR, G_DIR_SEPARATOR_S,
539 session->ac_prefs->recv_server,
540 "-", uid, NULL);
541 g_free(uid);
542 if ((pfile = prefs_file_open(path)) == NULL) {
543 g_free(path);
544 return -1;
545 }
546 prefs_file_set_backup_generation(pfile, 0);
547
548 for (n = 1; n <= session->count; n++) {
549 msg = &session->msg[n];
550 if (!msg->uidl || !msg->received)
551 continue;
552 if (session->state == POP3_DONE && msg->deleted)
553 continue;
554 fprintf(pfile->fp, "%s\t%ld\n", msg->uidl, msg->recv_time);
555 }
556
557 if (prefs_file_close(pfile) < 0)
558 g_warning("%s: failed to write UIDL list.\n", path);
559
560 g_free(path);
561
562 return 0;
563}
564
565gint pop3_write_msg_to_file(const gchar *file, FILE *src_fp, guint len)
566{
567 FILE *fp;
568 gchar buf[BUFFSIZE];
569 gchar last_ch = '\0';
570
571 g_return_val_if_fail(file != NULL, -1);
572
573 if ((fp = g_fopen(file, "wb")) == NULL) {
574 FILE_OP_ERROR(file, "fopen");
575 return -1;
576 }
577
578 if (change_file_mode_rw(fp, file) < 0)
579 FILE_OP_ERROR(file, "chmod");
580
581 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
582 gchar *p = buf;
583 gint len;
584
585 len = strlen(buf);
586 if (len > 0) {
587 last_ch = buf[len - 1];
588 if (last_ch == '\n' && len > 1 &&
589 buf[len - 2] == '\r') {
590 buf[len - 2] = '\n';
591 buf[len - 1] = '\0';
592 } else if (last_ch == '\r')
593 buf[len - 1] = '\0';
594 } else
595 last_ch = '\0';
596
597 if ((last_ch == '\0' || last_ch == '\n') &&
598 *p == '.' && *(p + 1) == '.')
599 p++;
600
601 if (fputs(p, fp) == EOF) {
602 FILE_OP_ERROR(file, "fputs");
603 g_warning("can't write to file: %s\n", file);
604 fclose(fp);
605 g_unlink(file);
606 return -1;
607 }
608 }
609
610 if (ferror(src_fp)) {
611 FILE_OP_ERROR(file, "fgets");
612 fclose(fp);
613 g_unlink(file);
614 return -1;
615 }
616
617 if (fclose(fp) == EOF) {
618 FILE_OP_ERROR(file, "fclose");
619 g_unlink(file);
620 return -1;
621 }
622
623 return 0;
624}
625
626static Pop3State pop3_lookup_next(Pop3Session *session)
627{
628 Pop3MsgInfo *msg;
629 PrefsAccount *ac = session->ac_prefs;
630 gint size;
631 gboolean size_limit_over;
632
633 for (;;) {
634 msg = &session->msg[session->cur_msg];
635 size = msg->size;
636 size_limit_over =
637 (ac->enable_size_limit &&
638 ac->size_limit > 0 &&
639 size > ac->size_limit * 1024);
640
641 if (msg->recv_time == RECV_TIME_DELETE ||
642 (ac->rmmail &&
643 msg->recv_time != RECV_TIME_NONE &&
644 msg->recv_time != RECV_TIME_KEEP &&
645 session->current_time - msg->recv_time >=
646 ac->msg_leave_time * 24 * 60 * 60)) {
647 log_print(_("POP3: Deleting expired message %d\n"),
648 session->cur_msg);
649 session->cur_total_bytes += size;
650 pop3_delete_send(session);
651 return POP3_DELETE;
652 }
653
654 if (size_limit_over && !msg->received) {
655 log_print
656 (_("POP3: Skipping message %d (%d bytes)\n"),
657 session->cur_msg, size);
658 session->skipped_num++;
659 }
660
661 if (size == 0 || msg->received || size_limit_over) {
662 session->cur_total_bytes += size;
663 if (session->cur_msg == session->count) {
664 pop3_logout_send(session);
665 return POP3_LOGOUT;
666 } else
667 session->cur_msg++;
668 } else
669 break;
670 }
671
672 pop3_retr_send(session);
673
674 return POP3_RETR;
675}
676
677Pop3ErrorValue pop3_ok(Pop3Session *session, const gchar *msg)
678{
679 Pop3ErrorValue ok;
680
681 log_print("POP3< %s\n", msg);
682
683 if (!strncmp(msg, "+OK", 3))
684 ok = PS_SUCCESS;
685 else if (!strncmp(msg, "-ERR", 4)) {
686 if (strstr(msg + 4, "lock") ||
687 strstr(msg + 4, "Lock") ||
688 strstr(msg + 4, "LOCK") ||
689 strstr(msg + 4, "wait")) {
690 log_warning(_("mailbox is locked\n"));
691 ok = PS_LOCKBUSY;
692 } else if (strcasestr(msg + 4, "timeout")) {
693 log_warning(_("session timeout\n"));
694 ok = PS_ERROR;
695 } else {
696 switch (session->state) {
697#if USE_SSL
698 case POP3_STLS:
699 log_warning(_("can't start TLS session\n"));
700 ok = PS_ERROR;
701 break;
702#endif
703 case POP3_GETAUTH_USER:
704 case POP3_GETAUTH_PASS:
705 case POP3_GETAUTH_APOP:
706 log_warning(_("error occurred on authentication\n"));
707 ok = PS_AUTHFAIL;
708 break;
709 case POP3_GETRANGE_LAST:
710 case POP3_GETRANGE_UIDL:
711 log_warning(_("command not supported\n"));
712 ok = PS_NOTSUPPORTED;
713 break;
714 default:
715 log_warning(_("error occurred on POP3 session\n"));
716 ok = PS_ERROR;
717 }
718 }
719
720 g_free(session->error_msg);
721 session->error_msg = g_strdup(msg);
722 fprintf(stderr, "POP3: %s\n", msg);
723 } else
724 ok = PS_PROTOCOL;
725
726 /* don't overwrite previous error on logout */
727 if (session->state != POP3_LOGOUT)
728 session->error_val = ok;
729
730 return ok;
731}
732
733static gint pop3_session_recv_msg(Session *session, const gchar *msg)
734{
735 Pop3Session *pop3_session = POP3_SESSION(session);
736 gint val = PS_SUCCESS;
737 const gchar *body;
738
739 body = msg;
740 if (pop3_session->state != POP3_GETRANGE_UIDL_RECV &&
741 pop3_session->state != POP3_GETSIZE_LIST_RECV) {
742 val = pop3_ok(pop3_session, msg);
743 if (val != PS_SUCCESS) {
744 if (val == PS_SOCKET) {
745 pop3_session->state = POP3_ERROR;
746 return -1;
747 }
748 if (val != PS_NOTSUPPORTED) {
749 if (pop3_session->state != POP3_LOGOUT) {
750 if (pop3_logout_send(pop3_session) == PS_SUCCESS)
751 return 0;
752 else
753 return -1;
754 }
755 }
756 }
757
758 if (*body == '+' || *body == '-')
759 body++;
760 while (g_ascii_isalpha(*body))
761 body++;
762 while (g_ascii_isspace(*body))
763 body++;
764 }
765
766 switch (pop3_session->state) {
767 case POP3_READY:
768 case POP3_GREETING:
769 val = pop3_greeting_recv(pop3_session, body);
770#if USE_SSL
771 if (pop3_session->ac_prefs->ssl_pop == SSL_STARTTLS)
772 val = pop3_stls_send(pop3_session);
773 else
774#endif
775 if (pop3_session->ac_prefs->use_apop_auth)
776 val = pop3_getauth_apop_send(pop3_session);
777 else
778 val = pop3_getauth_user_send(pop3_session);
779 break;
780#if USE_SSL
781 case POP3_STLS:
782 if ((val = pop3_stls_recv(pop3_session)) != PS_SUCCESS)
783 return -1;
784 if (pop3_session->ac_prefs->use_apop_auth)
785 val = pop3_getauth_apop_send(pop3_session);
786 else
787 val = pop3_getauth_user_send(pop3_session);
788 break;
789#endif
790 case POP3_GETAUTH_USER:
791 val = pop3_getauth_pass_send(pop3_session);
792 break;
793 case POP3_GETAUTH_PASS:
794 case POP3_GETAUTH_APOP:
795 if (pop3_session->auth_only)
796 val = pop3_logout_send(pop3_session);
797 else
798 val = pop3_getrange_stat_send(pop3_session);
799 break;
800 case POP3_GETRANGE_STAT:
801 if ((val = pop3_getrange_stat_recv(pop3_session, body)) != PS_SUCCESS)
802 return -1;
803 if (pop3_session->count > 0)
804 val = pop3_getrange_uidl_send(pop3_session);
805 else
806 val = pop3_logout_send(pop3_session);
807 break;
808 case POP3_GETRANGE_LAST:
809 if (val == PS_NOTSUPPORTED)
810 pop3_session->error_val = PS_SUCCESS;
811 else if ((val = pop3_getrange_last_recv
812 (pop3_session, body)) != PS_SUCCESS)
813 return -1;
814 if (pop3_session->cur_msg > 0)
815 val = pop3_getsize_list_send(pop3_session);
816 else
817 val = pop3_logout_send(pop3_session);
818 break;
819 case POP3_GETRANGE_UIDL:
820 if (val == PS_NOTSUPPORTED) {
821 pop3_session->error_val = PS_SUCCESS;
822 val = pop3_getrange_last_send(pop3_session);
823 } else {
824 pop3_session->state = POP3_GETRANGE_UIDL_RECV;
825 val = session_recv_data(session, 0, ".\r\n");
826 }
827 break;
828 case POP3_GETSIZE_LIST:
829 pop3_session->state = POP3_GETSIZE_LIST_RECV;
830 val = session_recv_data(session, 0, ".\r\n");
831 break;
832 case POP3_RETR:
833 pop3_session->state = POP3_RETR_RECV;
834 val = session_recv_data_as_file(session, 0, ".\r\n");
835 break;
836 case POP3_DELETE:
837 val = pop3_delete_recv(pop3_session);
838 if (pop3_session->cur_msg == pop3_session->count)
839 val = pop3_logout_send(pop3_session);
840 else {
841 pop3_session->cur_msg++;
842 if (pop3_lookup_next(pop3_session) == POP3_ERROR)
843 return -1;
844 }
845 break;
846 case POP3_LOGOUT:
847 if (val == PS_SUCCESS)
848 pop3_session->state = POP3_DONE;
849 else
850 pop3_session->state = POP3_ERROR;
851 session_disconnect(session);
852 break;
853 case POP3_ERROR:
854 default:
855 return -1;
856 }
857
858 if (val == PS_SUCCESS)
859 return 0;
860 else
861 return -1;
862}
863
864static gint pop3_session_recv_data_finished(Session *session, guchar *data,
865 guint len)
866{
867 Pop3Session *pop3_session = POP3_SESSION(session);
868 Pop3ErrorValue val = PS_SUCCESS;
869
870 switch (pop3_session->state) {
871 case POP3_GETRANGE_UIDL_RECV:
872 val = pop3_getrange_uidl_recv(pop3_session, (gchar *)data, len);
873 if (val == PS_SUCCESS) {
874 if (pop3_session->new_msg_exist)
875 pop3_getsize_list_send(pop3_session);
876 else
877 pop3_logout_send(pop3_session);
878 } else
879 return -1;
880 break;
881 case POP3_GETSIZE_LIST_RECV:
882 val = pop3_getsize_list_recv(pop3_session, (gchar *)data, len);
883 if (val == PS_SUCCESS) {
884 if (pop3_lookup_next(pop3_session) == POP3_ERROR)
885 return -1;
886 } else
887 return -1;
888 break;
889 case POP3_ERROR:
890 default:
891 return -1;
892 }
893
894 return 0;
895}
896
897static gint pop3_session_recv_data_as_file_finished(Session *session, FILE *fp,
898 guint len)
899{
900 Pop3Session *pop3_session = POP3_SESSION(session);
901
902 g_return_val_if_fail(pop3_session->state == POP3_RETR_RECV, -1);
903
904 if (pop3_retr_recv(pop3_session, fp, len) != PS_SUCCESS)
905 return -1;
906
907 /* disconnected? */
908 if (!session->sock)
909 return -1;
910
911 if (pop3_session->msg[pop3_session->cur_msg].recv_time
912 == RECV_TIME_DELETE ||
913 (pop3_session->ac_prefs->rmmail &&
914 pop3_session->ac_prefs->msg_leave_time == 0 &&
915 pop3_session->msg[pop3_session->cur_msg].recv_time
916 != RECV_TIME_KEEP))
917 pop3_delete_send(pop3_session);
918 else if (pop3_session->cur_msg == pop3_session->count)
919 pop3_logout_send(pop3_session);
920 else {
921 pop3_session->cur_msg++;
922 if (pop3_lookup_next(pop3_session) == POP3_ERROR)
923 return -1;
924 }
925
926 return 0;
927}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2006 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __POP_H__
21#define __POP_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28#include <time.h>
29
30#include "session.h"
31#include "prefs_account.h"
32#include "utils.h"
33
34typedef struct _Pop3MsgInfo Pop3MsgInfo;
35typedef struct _Pop3Session Pop3Session;
36
37#define POP3_SESSION(obj) ((Pop3Session *)obj)
38
39typedef enum {
40 POP3_READY,
41 POP3_GREETING,
42 POP3_STLS,
43 POP3_GETAUTH_USER,
44 POP3_GETAUTH_PASS,
45 POP3_GETAUTH_APOP,
46 POP3_GETRANGE_STAT,
47 POP3_GETRANGE_LAST,
48 POP3_GETRANGE_UIDL,
49 POP3_GETRANGE_UIDL_RECV,
50 POP3_GETSIZE_LIST,
51 POP3_GETSIZE_LIST_RECV,
52 POP3_RETR,
53 POP3_RETR_RECV,
54 POP3_DELETE,
55 POP3_LOGOUT,
56 POP3_DONE,
57 POP3_ERROR,
58
59 N_POP3_STATE
60} Pop3State;
61
62typedef enum {
63 PS_SUCCESS = 0, /* command successful */
64 PS_NOMAIL = 1, /* no mail available */
65 PS_SOCKET = 2, /* socket I/O woes */
66 PS_AUTHFAIL = 3, /* user authorization failed */
67 PS_PROTOCOL = 4, /* protocol violation */
68 PS_SYNTAX = 5, /* command-line syntax error */
69 PS_IOERR = 6, /* file I/O error */
70 PS_ERROR = 7, /* protocol error */
71 PS_EXCLUDE = 8, /* client-side exclusion error */
72 PS_LOCKBUSY = 9, /* server responded lock busy */
73 PS_SMTP = 10, /* SMTP error */
74 PS_DNS = 11, /* fatal DNS error */
75 PS_BSMTP = 12, /* output batch could not be opened */
76 PS_MAXFETCH = 13, /* poll ended by fetch limit */
77
78 PS_NOTSUPPORTED = 14, /* command not supported */
79
80 /* leave space for more codes */
81
82 PS_CONTINUE = 128 /* more responses may follow */
83} Pop3ErrorValue;
84
85typedef enum {
86 RECV_TIME_NONE = 0,
87 RECV_TIME_RECEIVED = 1,
88 RECV_TIME_KEEP = 2,
89 RECV_TIME_DELETE = 3
90} RecvTime;
91
92typedef enum {
93 DROP_OK = 0,
94 DROP_DONT_RECEIVE = 1,
95 DROP_DELETE = 2,
96 DROP_ERROR = -1
97} Pop3DropValue;
98
99struct _Pop3MsgInfo
100{
101 gint size;
102 gchar *uidl;
103 stime_t recv_time;
104 guint received : 1;
105 guint deleted : 1;
106};
107
108struct _Pop3Session
109{
110 Session session;
111
112 Pop3State state;
113
114 PrefsAccount *ac_prefs;
115
116 gchar *greeting;
117 gchar *user;
118 gchar *pass;
119 gint count;
120 gint64 total_bytes;
121 gint cur_msg;
122 gint cur_total_num;
123 gint64 cur_total_bytes;
124 gint64 cur_total_recv_bytes;
125 gint skipped_num;
126
127 Pop3MsgInfo *msg;
128
129 GHashTable *uidl_table;
130
131 gboolean auth_only;
132
133 gboolean new_msg_exist;
134 gboolean uidl_is_valid;
135
136 stime_t current_time;
137
138 Pop3ErrorValue error_val;
139 gchar *error_msg;
140
141 gpointer data;
142
143 /* virtual method to drop message */
144 gint (*drop_message) (Pop3Session *session,
145 const gchar *file);
146};
147
148#define POPBUFSIZE 512
149/* #define IDLEN 128 */
150#define IDLEN POPBUFSIZE
151
152Session *pop3_session_new (PrefsAccount *account);
153
154GHashTable *pop3_get_uidl_table (PrefsAccount *account);
155gint pop3_write_uidl_list (Pop3Session *session);
156
157#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2009 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include <glib.h>
25#include <glib/gi18n.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <unistd.h>
30#include <errno.h>
31
32#ifdef G_OS_WIN32
33# include <windows.h>
34# include <io.h>
35#endif
36
37#include "prefs.h"
38#include "codeconv.h"
39#include "utils.h"
40
41typedef enum
42{
43 DUMMY_PARAM
44} DummyEnum;
45
46typedef struct _PrefFilePrivate PrefFilePrivate;
47
48struct _PrefFilePrivate {
49 FILE *fp;
50 gchar *path;
51 gint backup_generation;
52};
53
54static void prefs_config_parse_one_line (GHashTable *param_table,
55 const gchar *buf);
56
57GHashTable *prefs_param_table_get(PrefParam *param)
58{
59 GHashTable *table;
60 gint i;
61
62 g_return_val_if_fail(param != NULL, NULL);
63
64 table = g_hash_table_new(g_str_hash, g_str_equal);
65
66 for (i = 0; param[i].name != NULL; i++) {
67 g_hash_table_insert(table, param[i].name, &param[i]);
68 }
69
70 return table;
71}
72
73void prefs_param_table_destroy(GHashTable *param_table)
74{
75 g_hash_table_destroy(param_table);
76}
77
78void prefs_read_config(PrefParam *param, const gchar *label,
79 const gchar *rcfile, const gchar *encoding)
80{
81 FILE *fp;
82 gchar buf[PREFSBUFSIZE];
83 gchar *block_label;
84 GHashTable *param_table;
85
86 g_return_if_fail(param != NULL);
87 g_return_if_fail(label != NULL);
88 g_return_if_fail(rcfile != NULL);
89
90 debug_print("Reading configuration...\n");
91
92 prefs_set_default(param);
93
94 if ((fp = g_fopen(rcfile, "rb")) == NULL) {
95 if (ENOENT != errno) FILE_OP_ERROR(rcfile, "fopen");
96 return;
97 }
98
99 block_label = g_strdup_printf("[%s]", label);
100
101 /* search aiming block */
102 while (fgets(buf, sizeof(buf), fp) != NULL) {
103 gint val;
104
105 if (encoding) {
106 gchar *conv_str;
107
108 conv_str = conv_codeset_strdup
109 (buf, encoding, CS_INTERNAL);
110 if (!conv_str)
111 conv_str = g_strdup(buf);
112 val = strncmp
113 (conv_str, block_label, strlen(block_label));
114 g_free(conv_str);
115 } else
116 val = strncmp(buf, block_label, strlen(block_label));
117 if (val == 0) {
118 debug_print("Found %s\n", block_label);
119 break;
120 }
121 }
122 g_free(block_label);
123
124 param_table = prefs_param_table_get(param);
125
126 while (fgets(buf, sizeof(buf), fp) != NULL) {
127 strretchomp(buf);
128 if (buf[0] == '\0') continue;
129 /* reached next block */
130 if (buf[0] == '[') break;
131
132 if (encoding) {
133 gchar *conv_str;
134
135 conv_str = conv_codeset_strdup
136 (buf, encoding, CS_INTERNAL);
137 if (!conv_str)
138 conv_str = g_strdup(buf);
139 prefs_config_parse_one_line(param_table, conv_str);
140 g_free(conv_str);
141 } else
142 prefs_config_parse_one_line(param_table, buf);
143 }
144
145 prefs_param_table_destroy(param_table);
146
147 debug_print("Finished reading configuration.\n");
148 fclose(fp);
149}
150
151static void prefs_config_parse_one_line(GHashTable *param_table,
152 const gchar *buf)
153{
154 PrefParam *param;
155 const gchar *p = buf;
156 gchar *name;
157 const gchar *value;
158
159 while (*p && *p != '=')
160 p++;
161
162 if (*p != '=') {
163 g_warning("invalid pref line: %s\n", buf);
164 return;
165 }
166
167 name = g_strndup(buf, p - buf);
168 value = p + 1;
169
170 /* debug_print("%s = %s\n", name, value); */
171
172 param = g_hash_table_lookup(param_table, name);
173
174 if (!param) {
175 debug_print("pref key '%s' (value '%s') not found\n",
176 name, value);
177 g_free(name);
178 return;
179 }
180
181 switch (param->type) {
182 case P_STRING:
183 g_free(*((gchar **)param->data));
184 *((gchar **)param->data) = *value ? g_strdup(value) : NULL;
185 break;
186 case P_INT:
187 *((gint *)param->data) = (gint)atoi(value);
188 break;
189 case P_BOOL:
190 *((gboolean *)param->data) =
191 (*value == '0' || *value == '\0') ? FALSE : TRUE;
192 break;
193 case P_ENUM:
194 *((DummyEnum *)param->data) = (DummyEnum)atoi(value);
195 break;
196 case P_USHORT:
197 *((gushort *)param->data) = (gushort)atoi(value);
198 break;
199 default:
200 break;
201 }
202
203 g_free(name);
204}
205
206#define TRY(func) \
207if (!(func)) \
208{ \
209 g_warning(_("failed to write configuration to file\n")); \
210 if (orig_fp) fclose(orig_fp); \
211 prefs_file_close_revert(pfile); \
212 g_free(rcpath); \
213 g_free(block_label); \
214 return; \
215} \
216
217void prefs_write_config(PrefParam *param, const gchar *label,
218 const gchar *rcfile)
219{
220 FILE *orig_fp;
221 PrefFile *pfile;
222 gchar *rcpath;
223 gchar buf[PREFSBUFSIZE];
224 gchar *block_label = NULL;
225 gboolean block_matched = FALSE;
226
227 g_return_if_fail(param != NULL);
228 g_return_if_fail(label != NULL);
229 g_return_if_fail(rcfile != NULL);
230
231 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, rcfile, NULL);
232 if ((orig_fp = g_fopen(rcpath, "rb")) == NULL) {
233 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
234 }
235
236 if ((pfile = prefs_file_open(rcpath)) == NULL) {
237 g_warning(_("failed to write configuration to file\n"));
238 if (orig_fp) fclose(orig_fp);
239 g_free(rcpath);
240 return;
241 }
242
243 block_label = g_strdup_printf("[%s]", label);
244
245 /* search aiming block */
246 if (orig_fp) {
247 while (fgets(buf, sizeof(buf), orig_fp) != NULL) {
248 gint val;
249
250 val = strncmp(buf, block_label, strlen(block_label));
251 if (val == 0) {
252 debug_print(_("Found %s\n"), block_label);
253 block_matched = TRUE;
254 break;
255 } else
256 TRY(fputs(buf, pfile->fp) != EOF);
257 }
258 }
259
260 TRY(fprintf(pfile->fp, "%s\n", block_label) > 0);
261 g_free(block_label);
262 block_label = NULL;
263
264 /* write all param data to file */
265 TRY(prefs_file_write_param(pfile, param) == 0);
266
267 if (block_matched) {
268 while (fgets(buf, sizeof(buf), orig_fp) != NULL) {
269 /* next block */
270 if (buf[0] == '[') {
271 TRY(fputc('\n', pfile->fp) != EOF &&
272 fputs(buf, pfile->fp) != EOF);
273 break;
274 }
275 }
276 while (fgets(buf, sizeof(buf), orig_fp) != NULL)
277 TRY(fputs(buf, pfile->fp) != EOF);
278 }
279
280 if (orig_fp) fclose(orig_fp);
281 if (prefs_file_close(pfile) < 0)
282 g_warning(_("failed to write configuration to file\n"));
283 g_free(rcpath);
284
285 debug_print(_("Configuration is saved.\n"));
286}
287
288gint prefs_file_write_param(PrefFile *pfile, PrefParam *param)
289{
290 gint i;
291 gchar buf[PREFSBUFSIZE];
292
293 for (i = 0; param[i].name != NULL; i++) {
294 switch (param[i].type) {
295 case P_STRING:
296 g_snprintf(buf, sizeof(buf), "%s=%s\n", param[i].name,
297 *((gchar **)param[i].data) ?
298 *((gchar **)param[i].data) : "");
299 break;
300 case P_INT:
301 g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name,
302 *((gint *)param[i].data));
303 break;
304 case P_BOOL:
305 g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name,
306 *((gboolean *)param[i].data));
307 break;
308 case P_ENUM:
309 g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name,
310 *((DummyEnum *)param[i].data));
311 break;
312 case P_USHORT:
313 g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name,
314 *((gushort *)param[i].data));
315 break;
316 default:
317 buf[0] = '\0';
318 }
319
320 if (buf[0] != '\0') {
321 if (fputs(buf, pfile->fp) == EOF) {
322 perror("fputs");
323 return -1;
324 }
325 }
326 }
327
328 return 0;
329}
330
331PrefFile *prefs_file_open(const gchar *path)
332{
333 PrefFilePrivate *pfile;
334 gchar *tmppath;
335 FILE *fp;
336
337 g_return_val_if_fail(path != NULL, NULL);
338
339 tmppath = g_strconcat(path, ".tmp", NULL);
340 if ((fp = g_fopen(tmppath, "wb")) == NULL) {
341 FILE_OP_ERROR(tmppath, "fopen");
342 g_free(tmppath);
343 return NULL;
344 }
345
346 if (change_file_mode_rw(fp, tmppath) < 0)
347 FILE_OP_ERROR(tmppath, "chmod");
348
349 g_free(tmppath);
350
351 pfile = g_new(PrefFilePrivate, 1);
352 pfile->fp = fp;
353 pfile->path = g_strdup(path);
354 pfile->backup_generation = 4;
355
356 return (PrefFile *)pfile;
357}
358
359void prefs_file_set_backup_generation(PrefFile *pfile, gint generation)
360{
361 PrefFilePrivate *priv = (PrefFilePrivate *)pfile;
362
363 g_return_if_fail(pfile != NULL);
364
365 priv->backup_generation = generation;
366}
367
368gint prefs_file_get_backup_generation(PrefFile *pfile)
369{
370 PrefFilePrivate *priv = (PrefFilePrivate *)pfile;
371
372 g_return_val_if_fail(pfile != NULL, -1);
373
374 return priv->backup_generation;
375}
376
377gint prefs_file_close(PrefFile *pfile)
378{
379 PrefFilePrivate *priv = (PrefFilePrivate *)pfile;
380 FILE *fp;
381 gchar *path;
382 gchar *tmppath;
383 gchar *bakpath = NULL;
384 gint generation;
385 gint ret = 0;
386
387 g_return_val_if_fail(pfile != NULL, -1);
388
389 fp = pfile->fp;
390 path = pfile->path;
391 generation = priv->backup_generation;
392 g_free(pfile);
393
394 tmppath = g_strconcat(path, ".tmp", NULL);
395 if (fflush(fp) == EOF) {
396 FILE_OP_ERROR(tmppath, "fflush");
397 fclose(fp);
398 ret = -1;
399 goto finish;
400 }
401#if HAVE_FSYNC
402 if (fsync(fileno(fp)) < 0) {
403 FILE_OP_ERROR(tmppath, "fsync");
404 fclose(fp);
405 ret = -1;
406 goto finish;
407 }
408#elif defined(G_OS_WIN32)
409 if (_commit(_fileno(fp)) < 0) {
410 FILE_OP_ERROR(tmppath, "_commit");
411 fclose(fp);
412 ret = -1;
413 goto finish;
414 }
415#endif
416 if (fclose(fp) == EOF) {
417 FILE_OP_ERROR(tmppath, "fclose");
418 ret = -1;
419 goto finish;
420 }
421
422 if (is_file_exist(path)) {
423 bakpath = g_strconcat(path, ".bak", NULL);
424 if (is_file_exist(bakpath)) {
425 gint i;
426 gchar *bakpath_n, *bakpath_p;
427
428 for (i = generation; i > 0; i--) {
429 bakpath_n = g_strdup_printf("%s.%d", bakpath,
430 i);
431 if (i == 1)
432 bakpath_p = g_strdup(bakpath);
433 else
434 bakpath_p = g_strdup_printf
435 ("%s.%d", bakpath, i - 1);
436 if (is_file_exist(bakpath_p)) {
437 if (rename_force(bakpath_p, bakpath_n) < 0) {
438 FILE_OP_ERROR(bakpath_p,
439 "rename");
440 }
441 }
442 g_free(bakpath_p);
443 g_free(bakpath_n);
444 }
445 }
446 if (rename_force(path, bakpath) < 0) {
447 FILE_OP_ERROR(path, "rename");
448 ret = -1;
449 goto finish;
450 }
451 }
452
453 if (rename_force(tmppath, path) < 0) {
454 FILE_OP_ERROR(tmppath, "rename");
455 ret = -1;
456 goto finish;
457 }
458
459finish:
460 if (ret < 0)
461 g_unlink(tmppath);
462 g_free(path);
463 g_free(tmppath);
464 g_free(bakpath);
465 return ret;
466}
467
468gint prefs_file_close_revert(PrefFile *pfile)
469{
470 gchar *tmppath;
471
472 g_return_val_if_fail(pfile != NULL, -1);
473
474 tmppath = g_strconcat(pfile->path, ".tmp", NULL);
475 fclose(pfile->fp);
476 if (g_unlink(tmppath) < 0)
477 FILE_OP_ERROR(tmppath, "unlink");
478 g_free(tmppath);
479 g_free(pfile->path);
480 g_free(pfile);
481
482 return 0;
483}
484
485void prefs_set_default(PrefParam *param)
486{
487 gint i;
488
489 g_return_if_fail(param != NULL);
490
491 for (i = 0; param[i].name != NULL; i++) {
492 if (!param[i].data) continue;
493
494 switch (param[i].type) {
495 case P_STRING:
496 if (param[i].defval != NULL) {
497 if (!g_ascii_strncasecmp(param[i].defval, "ENV_", 4)) {
498 const gchar *envstr;
499 gchar *tmp = NULL;
500
501 envstr = g_getenv(param[i].defval + 4);
502#ifdef G_OS_WIN32
503 tmp = g_strdup(envstr);
504#else
505 if (envstr) {
506 tmp = conv_codeset_strdup
507 (envstr,
508 conv_get_locale_charset_str(),
509 CS_UTF_8);
510 if (!tmp) {
511 g_warning("failed to convert character set.");
512 tmp = g_strdup(envstr);
513 }
514 }
515#endif
516 *((gchar **)param[i].data) = tmp;
517 } else if (param[i].defval[0] == '~')
518 *((gchar **)param[i].data) =
519#ifdef G_OS_WIN32
520 g_strconcat(get_rc_dir(),
521#else
522 g_strconcat(get_home_dir(),
523#endif
524 param[i].defval + 1,
525 NULL);
526 else if (param[i].defval[0] != '\0')
527 *((gchar **)param[i].data) =
528 g_strdup(param[i].defval);
529 else
530 *((gchar **)param[i].data) = NULL;
531 } else
532 *((gchar **)param[i].data) = NULL;
533 break;
534 case P_INT:
535 if (param[i].defval != NULL)
536 *((gint *)param[i].data) =
537 (gint)atoi(param[i].defval);
538 else
539 *((gint *)param[i].data) = 0;
540 break;
541 case P_BOOL:
542 if (param[i].defval != NULL) {
543 if (!g_ascii_strcasecmp(param[i].defval, "TRUE"))
544 *((gboolean *)param[i].data) = TRUE;
545 else
546 *((gboolean *)param[i].data) =
547 atoi(param[i].defval) ? TRUE : FALSE;
548 } else
549 *((gboolean *)param[i].data) = FALSE;
550 break;
551 case P_ENUM:
552 if (param[i].defval != NULL)
553 *((DummyEnum*)param[i].data) =
554 (DummyEnum)atoi(param[i].defval);
555 else
556 *((DummyEnum *)param[i].data) = 0;
557 break;
558 case P_USHORT:
559 if (param[i].defval != NULL)
560 *((gushort *)param[i].data) =
561 (gushort)atoi(param[i].defval);
562 else
563 *((gushort *)param[i].data) = 0;
564 break;
565 default:
566 break;
567 }
568 }
569}
570
571void prefs_free(PrefParam *param)
572{
573 gint i;
574
575 g_return_if_fail(param != NULL);
576
577 for (i = 0; param[i].name != NULL; i++) {
578 if (!param[i].data) continue;
579
580 switch (param[i].type) {
581 case P_STRING:
582 g_free(*((gchar **)param[i].data));
583 break;
584 default:
585 break;
586 }
587 }
588}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2008 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __PREFS_H__
21#define __PREFS_H__
22
23#include <glib.h>
24#include <stdio.h>
25
26typedef struct _PrefParam PrefParam;
27typedef struct _PrefFile PrefFile;
28
29#define PREFSBUFSIZE 8192
30
31typedef enum
32{
33 P_STRING,
34 P_INT,
35 P_BOOL,
36 P_ENUM,
37 P_USHORT,
38 P_OTHER
39} PrefType;
40
41typedef void (*DataSetFunc) (PrefParam *pparam);
42typedef void (*WidgetSetFunc) (PrefParam *pparam);
43
44struct _PrefParam {
45 gchar *name;
46 gchar *defval;
47 gpointer data;
48 PrefType type;
49 gpointer ui_data;
50};
51
52struct _PrefFile {
53 FILE *fp;
54 gchar *path;
55};
56
57GHashTable *prefs_param_table_get (PrefParam *param);
58void prefs_param_table_destroy (GHashTable *param_table);
59
60void prefs_read_config (PrefParam *param,
61 const gchar *label,
62 const gchar *rcfile,
63 const gchar *encoding);
64void prefs_write_config (PrefParam *param,
65 const gchar *label,
66 const gchar *rcfile);
67
68PrefFile *prefs_file_open (const gchar *path);
69gint prefs_file_write_param (PrefFile *pfile,
70 PrefParam *param);
71void prefs_file_set_backup_generation (PrefFile *pfile,
72 gint generation);
73gint prefs_file_get_backup_generation (PrefFile *pfile);
74gint prefs_file_close (PrefFile *pfile);
75gint prefs_file_close_revert (PrefFile *pfile);
76
77void prefs_set_default (PrefParam *param);
78void prefs_free (PrefParam *param);
79
80#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2017 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <stdio.h>
29#include <string.h>
30#include <stdlib.h>
31
32#include "prefs.h"
33#include "prefs_account.h"
34#include "customheader.h"
35#include "account.h"
36#include "utils.h"
37
38static PrefsAccount tmp_ac_prefs;
39
40static PrefParam param[] = {
41 /* Basic */
42 {"account_name", NULL, &tmp_ac_prefs.account_name, P_STRING},
43 {"is_default", "FALSE", &tmp_ac_prefs.is_default, P_BOOL},
44 {"name", NULL, &tmp_ac_prefs.name, P_STRING},
45 {"address", NULL, &tmp_ac_prefs.address, P_STRING},
46 {"organization", NULL, &tmp_ac_prefs.organization, P_STRING},
47 {"protocol", NULL, &tmp_ac_prefs.protocol, P_ENUM},
48 {"receive_server", NULL, &tmp_ac_prefs.recv_server, P_STRING},
49 {"smtp_server", NULL, &tmp_ac_prefs.smtp_server, P_STRING},
50 {"nntp_server", NULL, &tmp_ac_prefs.nntp_server, P_STRING},
51 {"use_nntp_auth", "FALSE", &tmp_ac_prefs.use_nntp_auth, P_BOOL},
52 {"user_id", "ENV_USER", &tmp_ac_prefs.userid, P_STRING},
53 {"password", NULL, &tmp_ac_prefs.passwd, P_STRING},
54 {"inbox", "inbox", &tmp_ac_prefs.inbox, P_STRING},
55
56 /* Receive */
57 {"use_apop_auth", "FALSE", &tmp_ac_prefs.use_apop_auth, P_BOOL},
58 {"remove_mail", "TRUE", &tmp_ac_prefs.rmmail, P_BOOL},
59 {"message_leave_time", "7", &tmp_ac_prefs.msg_leave_time, P_INT},
60 {"get_all_mail", "FALSE", &tmp_ac_prefs.getall, P_BOOL},
61 {"enable_size_limit", "FALSE", &tmp_ac_prefs.enable_size_limit, P_BOOL},
62 {"size_limit", "1024", &tmp_ac_prefs.size_limit, P_INT},
63 {"filter_on_receive", "TRUE", &tmp_ac_prefs.filter_on_recv, P_BOOL},
64 {"imap_check_inbox_only", "FALSE", &tmp_ac_prefs.imap_check_inbox_only,
65 P_BOOL},
66 {"imap_filter_inbox_on_receive", "FALSE",
67 &tmp_ac_prefs.imap_filter_inbox_on_recv, P_BOOL},
68 {"imap_auth_method", "0", &tmp_ac_prefs.imap_auth_type, P_ENUM},
69 {"max_nntp_articles", "300", &tmp_ac_prefs.max_nntp_articles, P_INT},
70 {"receive_at_get_all", "TRUE", &tmp_ac_prefs.recv_at_getall, P_BOOL},
71
72 /* Send */
73 {"add_date", "TRUE", &tmp_ac_prefs.add_date, P_BOOL},
74 {"generate_msgid", "TRUE", &tmp_ac_prefs.gen_msgid, P_BOOL},
75 {"add_custom_header", "FALSE", &tmp_ac_prefs.add_customhdr, P_BOOL},
76 {"use_smtp_auth", "FALSE", &tmp_ac_prefs.use_smtp_auth, P_BOOL},
77 {"smtp_auth_method", "0", &tmp_ac_prefs.smtp_auth_type, P_ENUM},
78 {"smtp_user_id", NULL, &tmp_ac_prefs.smtp_userid, P_STRING},
79 {"smtp_password", NULL, &tmp_ac_prefs.smtp_passwd, P_STRING},
80 {"pop_before_smtp", "FALSE", &tmp_ac_prefs.pop_before_smtp, P_BOOL},
81
82 /* Compose */
83 {"signature_type", "2", &tmp_ac_prefs.sig_type, P_ENUM},
84 {"signature_path", "~" G_DIR_SEPARATOR_S DEFAULT_SIGNATURE,
85 &tmp_ac_prefs.sig_path, P_STRING},
86 {"signature_name", NULL, &tmp_ac_prefs.sig_names[0], P_STRING},
87 {"signature_name2", NULL, &tmp_ac_prefs.sig_names[1], P_STRING},
88 {"signature_name3", NULL, &tmp_ac_prefs.sig_names[2], P_STRING},
89 {"signature_name4", NULL, &tmp_ac_prefs.sig_names[3], P_STRING},
90 {"signature_name5", NULL, &tmp_ac_prefs.sig_names[4], P_STRING},
91 {"signature_name6", NULL, &tmp_ac_prefs.sig_names[5], P_STRING},
92 {"signature_name7", NULL, &tmp_ac_prefs.sig_names[6], P_STRING},
93 {"signature_name8", NULL, &tmp_ac_prefs.sig_names[7], P_STRING},
94 {"signature_name9", NULL, &tmp_ac_prefs.sig_names[8], P_STRING},
95 {"signature_name10", NULL, &tmp_ac_prefs.sig_names[9], P_STRING},
96 {"signature_text", NULL, &tmp_ac_prefs.sig_text, P_STRING},
97 {"signature_text2", NULL, &tmp_ac_prefs.sig_texts[1], P_STRING},
98 {"signature_text3", NULL, &tmp_ac_prefs.sig_texts[2], P_STRING},
99 {"signature_text4", NULL, &tmp_ac_prefs.sig_texts[3], P_STRING},
100 {"signature_text5", NULL, &tmp_ac_prefs.sig_texts[4], P_STRING},
101 {"signature_text6", NULL, &tmp_ac_prefs.sig_texts[5], P_STRING},
102 {"signature_text7", NULL, &tmp_ac_prefs.sig_texts[6], P_STRING},
103 {"signature_text8", NULL, &tmp_ac_prefs.sig_texts[7], P_STRING},
104 {"signature_text9", NULL, &tmp_ac_prefs.sig_texts[8], P_STRING},
105 {"signature_text10", NULL, &tmp_ac_prefs.sig_texts[9], P_STRING},
106 {"signature_before_quote", "FALSE", &tmp_ac_prefs.sig_before_quote,
107 P_BOOL},
108 {"set_autocc", "FALSE", &tmp_ac_prefs.set_autocc, P_BOOL},
109 {"auto_cc", NULL, &tmp_ac_prefs.auto_cc, P_STRING},
110 {"set_autobcc", "FALSE", &tmp_ac_prefs.set_autobcc, P_BOOL},
111 {"auto_bcc", NULL, &tmp_ac_prefs.auto_bcc, P_STRING},
112 {"set_autoreplyto", "FALSE", &tmp_ac_prefs.set_autoreplyto, P_BOOL},
113 {"auto_replyto", NULL, &tmp_ac_prefs.auto_replyto, P_STRING},
114
115 /* Privacy */
116 {"default_sign", "FALSE", &tmp_ac_prefs.default_sign, P_BOOL},
117 {"default_encrypt", "FALSE", &tmp_ac_prefs.default_encrypt, P_BOOL},
118 {"encrypt_reply", "TRUE", &tmp_ac_prefs.encrypt_reply, P_BOOL},
119 {"encrypt_to_self", "TRUE", &tmp_ac_prefs.encrypt_to_self, P_BOOL},
120 {"ascii_armored", "FALSE", &tmp_ac_prefs.ascii_armored, P_BOOL},
121 {"clearsign", "FALSE", &tmp_ac_prefs.clearsign, P_BOOL},
122 {"sign_key", NULL, &tmp_ac_prefs.sign_key, P_ENUM},
123 {"sign_key_id", NULL, &tmp_ac_prefs.sign_key_id, P_STRING},
124
125 /* SSL */
126 {"ssl_pop", "0", &tmp_ac_prefs.ssl_pop, P_ENUM},
127 {"ssl_imap", "0", &tmp_ac_prefs.ssl_imap, P_ENUM},
128 {"ssl_nntp", "0", &tmp_ac_prefs.ssl_nntp, P_ENUM},
129 {"ssl_smtp", "0", &tmp_ac_prefs.ssl_smtp, P_ENUM},
130 {"use_nonblocking_ssl", "1", &tmp_ac_prefs.use_nonblocking_ssl, P_BOOL},
131
132 /* SOCKS proxy */
133 {"use_socks", "FALSE", &tmp_ac_prefs.use_socks, P_BOOL},
134 {"use_socks_for_recv", "TRUE", &tmp_ac_prefs.use_socks_for_recv, P_BOOL},
135 {"use_socks_for_send", "TRUE", &tmp_ac_prefs.use_socks_for_send, P_BOOL},
136 {"socks_type", "1", &tmp_ac_prefs.socks_type, P_ENUM},
137 {"proxy_host", NULL, &tmp_ac_prefs.proxy_host, P_STRING},
138 {"proxy_port", "1080", &tmp_ac_prefs.proxy_port, P_USHORT},
139 {"use_proxy_auth", "FALSE", &tmp_ac_prefs.use_proxy_auth, P_BOOL},
140 {"proxy_name", NULL, &tmp_ac_prefs.proxy_name, P_STRING},
141 {"proxy_pass", NULL, &tmp_ac_prefs.proxy_pass, P_STRING},
142
143 /* Advanced */
144 {"set_smtpport", "FALSE", &tmp_ac_prefs.set_smtpport, P_BOOL},
145 {"smtp_port", "25", &tmp_ac_prefs.smtpport, P_USHORT},
146 {"set_popport", "FALSE", &tmp_ac_prefs.set_popport, P_BOOL},
147 {"pop_port", "110", &tmp_ac_prefs.popport, P_USHORT},
148 {"set_imapport", "FALSE", &tmp_ac_prefs.set_imapport, P_BOOL},
149 {"imap_port", "143", &tmp_ac_prefs.imapport, P_USHORT},
150 {"set_nntpport", "FALSE", &tmp_ac_prefs.set_nntpport, P_BOOL},
151 {"nntp_port", "119", &tmp_ac_prefs.nntpport, P_USHORT},
152 {"set_domain", "FALSE", &tmp_ac_prefs.set_domain, P_BOOL},
153 {"domain", NULL, &tmp_ac_prefs.domain, P_STRING},
154 {"imap_directory", NULL, &tmp_ac_prefs.imap_dir, P_STRING},
155 {"imap_clear_cache_on_exit", "FALSE",
156 &tmp_ac_prefs.imap_clear_cache_on_exit, P_BOOL},
157 {"set_sent_folder", "FALSE", &tmp_ac_prefs.set_sent_folder, P_BOOL},
158 {"sent_folder", NULL, &tmp_ac_prefs.sent_folder, P_STRING},
159 {"set_draft_folder", "FALSE", &tmp_ac_prefs.set_draft_folder, P_BOOL},
160 {"draft_folder", NULL, &tmp_ac_prefs.draft_folder, P_STRING},
161 {"set_queue_folder", "FALSE", &tmp_ac_prefs.set_queue_folder, P_BOOL},
162 {"queue_folder", NULL, &tmp_ac_prefs.queue_folder, P_STRING},
163 {"set_trash_folder", "FALSE", &tmp_ac_prefs.set_trash_folder, P_BOOL},
164 {"trash_folder", NULL, &tmp_ac_prefs.trash_folder, P_STRING},
165
166 {NULL, NULL, NULL, P_OTHER}
167};
168
169static gint prefs_account_get_new_id(void);
170
171
172PrefsAccount *prefs_account_get_tmp_prefs(void)
173{
174 return &tmp_ac_prefs;
175}
176
177void prefs_account_set_tmp_prefs(PrefsAccount *ac_prefs)
178{
179 tmp_ac_prefs = *ac_prefs;
180}
181
182void prefs_account_apply_tmp_prefs(PrefsAccount *ac_prefs)
183{
184 *ac_prefs = tmp_ac_prefs;
185}
186
187PrefParam *prefs_account_get_params(void)
188{
189 return param;
190}
191
192PrefsAccount *prefs_account_new(void)
193{
194 PrefsAccount *ac_prefs;
195
196 ac_prefs = g_new0(PrefsAccount, 1);
197 memset(&tmp_ac_prefs, 0, sizeof(PrefsAccount));
198 prefs_set_default(param);
199 *ac_prefs = tmp_ac_prefs;
200 ac_prefs->account_id = prefs_account_get_new_id();
201
202 return ac_prefs;
203}
204
205void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label)
206{
207 const gchar *p = label;
208 gchar *rcpath;
209 gint id;
210
211 g_return_if_fail(ac_prefs != NULL);
212 g_return_if_fail(label != NULL);
213
214 memset(&tmp_ac_prefs, 0, sizeof(PrefsAccount));
215
216 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACCOUNT_RC, NULL);
217 prefs_read_config(param, label, rcpath, NULL);
218 g_free(rcpath);
219
220 *ac_prefs = tmp_ac_prefs;
221 while (*p && !g_ascii_isdigit(*p)) p++;
222 id = atoi(p);
223 if (id < 0) g_warning("wrong account id: %d\n", id);
224 ac_prefs->account_id = id;
225
226 if (ac_prefs->protocol == A_APOP) {
227 debug_print("converting protocol A_APOP to new prefs.\n");
228 ac_prefs->protocol = A_POP3;
229 ac_prefs->use_apop_auth = TRUE;
230 }
231
232 custom_header_read_config(ac_prefs);
233}
234
235void prefs_account_write_config_all(GList *account_list)
236{
237 GList *cur;
238 gchar *rcpath;
239 PrefFile *pfile;
240
241 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACCOUNT_RC, NULL);
242 if ((pfile = prefs_file_open(rcpath)) == NULL) {
243 g_free(rcpath);
244 return;
245 }
246 g_free(rcpath);
247
248 for (cur = account_list; cur != NULL; cur = cur->next) {
249 tmp_ac_prefs = *(PrefsAccount *)cur->data;
250 if (fprintf(pfile->fp, "[Account: %d]\n",
251 tmp_ac_prefs.account_id) <= 0 ||
252 prefs_file_write_param(pfile, param) < 0) {
253 g_warning(_("failed to write configuration to file\n"));
254 prefs_file_close_revert(pfile);
255 return;
256 }
257 if (cur->next) {
258 if (fputc('\n', pfile->fp) == EOF) {
259 FILE_OP_ERROR(rcpath, "fputc");
260 prefs_file_close_revert(pfile);
261 return;
262 }
263 }
264 }
265
266 if (prefs_file_close(pfile) < 0)
267 g_warning(_("failed to write configuration to file\n"));
268}
269
270void prefs_account_free(PrefsAccount *ac_prefs)
271{
272 if (!ac_prefs) return;
273
274 tmp_ac_prefs = *ac_prefs;
275 prefs_free(param);
276
277 if (ac_prefs->tmp_pass)
278 g_free(ac_prefs->tmp_pass);
279 g_free(ac_prefs);
280}
281
282static gint prefs_account_get_new_id(void)
283{
284 GList *ac_list;
285 PrefsAccount *ac;
286 static gint last_id = 0;
287
288 for (ac_list = account_get_list(); ac_list != NULL;
289 ac_list = ac_list->next) {
290 ac = (PrefsAccount *)ac_list->data;
291 if (last_id < ac->account_id)
292 last_id = ac->account_id;
293 }
294
295 return last_id + 1;
296}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2017 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __PREFS_ACCOUNT_H__
21#define __PREFS_ACCOUNT_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28
29typedef struct _PrefsAccount PrefsAccount;
30
31#include "folder.h"
32#include "smtp.h"
33#include "prefs.h"
34
35typedef enum {
36 A_POP3,
37 A_APOP, /* deprecated */
38 A_RPOP, /* deprecated */
39 A_IMAP4,
40 A_NNTP,
41 A_LOCAL
42} RecvProtocol;
43
44typedef enum {
45 SIG_FILE,
46 SIG_COMMAND,
47 SIG_DIRECT
48} SigType;
49
50typedef enum {
51 SIGN_KEY_DEFAULT,
52 SIGN_KEY_BY_FROM,
53 SIGN_KEY_CUSTOM
54} SignKeyType;
55
56struct _PrefsAccount
57{
58 gchar *account_name;
59
60 /* Personal info */
61 gchar *name;
62 gchar *address;
63 gchar *organization;
64
65 /* Server info */
66 RecvProtocol protocol;
67 gchar *recv_server;
68 gchar *smtp_server;
69 gchar *nntp_server;
70 gboolean use_nntp_auth;
71 gchar *userid;
72 gchar *passwd;
73
74 /* SSL */
75 gint ssl_pop;
76 gint ssl_imap;
77 gint ssl_nntp;
78 gint ssl_smtp;
79 gboolean use_nonblocking_ssl;
80
81 /* Temporarily preserved password */
82 gchar *tmp_pass;
83
84 /* Receive */
85 gboolean use_apop_auth;
86 gboolean rmmail;
87 gint msg_leave_time;
88 gboolean getall;
89 gboolean enable_size_limit;
90 gint size_limit;
91 gboolean filter_on_recv;
92 gchar *inbox;
93
94 gboolean imap_check_inbox_only;
95 gboolean imap_filter_inbox_on_recv;
96 gint imap_auth_type;
97
98 gint max_nntp_articles;
99
100 gboolean recv_at_getall;
101
102 /* Send */
103 gboolean add_date;
104 gboolean gen_msgid;
105 gboolean add_customhdr;
106 gboolean use_smtp_auth;
107 SMTPAuthType smtp_auth_type;
108 gchar *smtp_userid;
109 gchar *smtp_passwd;
110
111 /* Temporarily preserved password */
112 gchar *tmp_smtp_pass;
113
114 gboolean pop_before_smtp;
115
116 GSList *customhdr_list;
117
118 /* Compose */
119 SigType sig_type;
120 gchar *sig_path;
121 gchar *sig_text;
122 gboolean set_autocc;
123 gchar *auto_cc;
124 gboolean set_autobcc;
125 gchar *auto_bcc;
126 gboolean set_autoreplyto;
127 gchar *auto_replyto;
128
129 /* Privacy */
130 gboolean default_sign;
131 gboolean default_encrypt;
132 gboolean ascii_armored;
133 gboolean clearsign;
134 gboolean encrypt_reply;
135
136 SignKeyType sign_key;
137 gchar *sign_key_id;
138
139 /* Advanced */
140 gboolean set_smtpport;
141 gushort smtpport;
142 gboolean set_popport;
143 gushort popport;
144 gboolean set_imapport;
145 gushort imapport;
146 gboolean set_nntpport;
147 gushort nntpport;
148 gboolean set_domain;
149 gchar *domain;
150
151 gchar *imap_dir;
152 gboolean imap_clear_cache_on_exit;
153
154 gboolean set_sent_folder;
155 gchar *sent_folder;
156 gboolean set_draft_folder;
157 gchar *draft_folder;
158 gboolean set_queue_folder;
159 gchar *queue_folder;
160 gboolean set_trash_folder;
161 gchar *trash_folder;
162
163 /* Default or not */
164 gboolean is_default;
165 /* Unique account ID */
166 gint account_id;
167
168 RemoteFolder *folder;
169
170 /* Compose */
171 gboolean sig_before_quote;
172
173 /* SOCKS proxy */
174 gboolean use_socks;
175 gboolean use_socks_for_recv;
176 gboolean use_socks_for_send;
177 gint socks_type;
178 gchar *proxy_host;
179 gushort proxy_port;
180 gboolean use_proxy_auth;
181 gchar *proxy_name;
182 gchar *proxy_pass;
183
184 /* Privacy */
185 gboolean encrypt_to_self;
186
187 /* Compose */
188 gchar *sig_names[10];
189 gchar *sig_texts[10];
190};
191
192PrefsAccount *prefs_account_new (void);
193
194PrefsAccount *prefs_account_get_tmp_prefs (void);
195void prefs_account_set_tmp_prefs (PrefsAccount *ac_prefs);
196void prefs_account_apply_tmp_prefs (PrefsAccount *ac_prefs);
197PrefParam *prefs_account_get_params (void);
198
199void prefs_account_read_config (PrefsAccount *ac_prefs,
200 const gchar *label);
201void prefs_account_write_config_all (GList *account_list);
202
203void prefs_account_free (PrefsAccount *ac_prefs);
204
205#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2017 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <stdio.h>
29#include <errno.h>
30
31#include "prefs.h"
32#include "prefs_common.h"
33#include "filter.h"
34#include "codeconv.h"
35#include "utils.h"
36
37PrefsCommon prefs_common;
38
39static PrefParam param[] = {
40 /* Receive */
41 {"autochk_newmail", "FALSE", &prefs_common.autochk_newmail, P_BOOL},
42 {"autochk_interval", "10", &prefs_common.autochk_itv, P_INT},
43 {"check_on_startup", "FALSE", &prefs_common.chk_on_startup, P_BOOL},
44 {"scan_all_after_inc", "FALSE", &prefs_common.scan_all_after_inc,
45 P_BOOL},
46 {"enable_newmsg_notify", "FALSE", &prefs_common.enable_newmsg_notify,
47 P_BOOL},
48 {"newmsg_notify_command", NULL, &prefs_common.newmsg_notify_cmd,
49 P_STRING},
50 {"enable_newmsg_notify_sound", "FALSE",
51 &prefs_common.enable_newmsg_notify_sound, P_BOOL},
52 {"newmsg_notify_sound", NULL, &prefs_common.newmsg_notify_sound,
53 P_STRING},
54 {"enable_newmsg_notify_window", "TRUE",
55 &prefs_common.enable_newmsg_notify_window, P_BOOL},
56 {"notify_window_period", "10",
57 &prefs_common.notify_window_period, P_INT},
58
59 {"inc_local", "FALSE", &prefs_common.inc_local, P_BOOL},
60 {"filter_on_inc_local", "TRUE", &prefs_common.filter_on_inc, P_BOOL},
61 {"spool_path", DEFAULT_SPOOL_PATH, &prefs_common.spool_path, P_STRING},
62
63 /* Send */
64 {"save_message", "TRUE", &prefs_common.savemsg, P_BOOL},
65 {"filter_sent_message", "FALSE", &prefs_common.filter_sent, P_BOOL},
66 {"recipients_autoreg", "TRUE", &prefs_common.recipients_autoreg,
67 P_BOOL},
68 {"show_send_dialog", "TRUE", &prefs_common.show_send_dialog, P_BOOL},
69
70 {"encoding_method", "0", &prefs_common.encoding_method, P_ENUM},
71 {"mime_filename_encoding_method", "0",
72 &prefs_common.mime_fencoding_method, P_ENUM},
73 {"check_attach", "FALSE", &prefs_common.check_attach, P_BOOL},
74 {"check_attach_str", NULL, &prefs_common.check_attach_str, P_STRING},
75 {"check_recipients", "FALSE", &prefs_common.check_recipients, P_BOOL},
76 {"check_recp_exclude", NULL, &prefs_common.check_recp_exclude,
77 P_STRING},
78
79 {"allow_jisx0201_kana", "FALSE", &prefs_common.allow_jisx0201_kana,
80 P_BOOL},
81
82 /* Compose */
83 {"auto_signature", "TRUE", &prefs_common.auto_sig, P_BOOL},
84 {"signature_separator", "-- ", &prefs_common.sig_sep, P_STRING},
85
86 {"auto_ext_editor", "FALSE", &prefs_common.auto_exteditor, P_BOOL},
87
88 {"undo_level", "50", &prefs_common.undolevels, P_INT},
89
90 {"linewrap_length", "72", &prefs_common.linewrap_len, P_INT},
91 {"linewrap_quotation", "FALSE", &prefs_common.linewrap_quote, P_BOOL},
92 {"linewrap_auto", "FALSE", &prefs_common.autowrap, P_BOOL},
93
94 {"enable_autosave", "FALSE", &prefs_common.enable_autosave, P_BOOL},
95 {"autosave_interval", "5", &prefs_common.autosave_itv, P_INT},
96
97 {"reply_with_quote", "TRUE", &prefs_common.reply_with_quote, P_BOOL},
98 {"reply_account_autoselect", "TRUE",
99 &prefs_common.reply_account_autosel, P_BOOL},
100 {"default_reply_list", "TRUE", &prefs_common.default_reply_list,
101 P_BOOL},
102 {"inherit_recipient_on_self_reply", "FALSE",
103 &prefs_common.inherit_recipient_on_self_reply, P_BOOL},
104 {"reply_address_only", "FALSE",
105 &prefs_common.reply_address_only, P_BOOL},
106
107 {"show_ruler", "TRUE", &prefs_common.show_ruler, P_BOOL},
108
109 /* Quote */
110 {"reply_quote_mark", "> ", &prefs_common.quotemark, P_STRING},
111 {"reply_quote_format", "On %d\\n%f wrote:\\n\\n%Q",
112 &prefs_common.quotefmt, P_STRING},
113
114 {"forward_quote_mark", "> ", &prefs_common.fw_quotemark, P_STRING},
115 {"forward_quote_format",
116 "\\n\\nBegin forwarded message:\\n\\n"
117 "?d{Date: %d\\n}?f{From: %f\\n}?t{To: %t\\n}?c{Cc: %c\\n}"
118 "?n{Newsgroups: %n\\n}?s{Subject: %s\\n}\\n\\n%M",
119 &prefs_common.fw_quotefmt, P_STRING},
120
121 /* Spelling */
122 {"check_spell", "FALSE", &prefs_common.check_spell, P_BOOL},
123 {"spell_lang", "en", &prefs_common.spell_lang, P_STRING},
124
125 /* Display */
126 {"message_font_name", DEFAULT_MESSAGE_FONT, &prefs_common.textfont,
127 P_STRING},
128
129 {"display_folder_unread_num", "TRUE",
130 &prefs_common.display_folder_unread, P_BOOL},
131 {"display_folder_num_columns", "FALSE",
132 &prefs_common.display_folder_num_columns, P_BOOL},
133 {"folder_col_show_new", "FALSE",
134 &prefs_common.folder_col_visible[1], P_BOOL},
135 {"folder_col_show_unread", "FALSE",
136 &prefs_common.folder_col_visible[2], P_BOOL},
137 {"folder_col_show_total", "FALSE",
138 &prefs_common.folder_col_visible[3], P_BOOL},
139
140 {"newsgroup_abbrev_len", "16", &prefs_common.ng_abbrev_len, P_INT},
141
142 {"translate_header", "TRUE", &prefs_common.trans_hdr, P_BOOL},
143
144 /* Display: Summary View */
145 {"enable_swap_from", "FALSE", &prefs_common.swap_from, P_BOOL},
146 {"date_format", "%y/%m/%d(%a) %H:%M", &prefs_common.date_format,
147 P_STRING},
148 {"expand_thread", "TRUE", &prefs_common.expand_thread, P_BOOL},
149
150 {"enable_rules_hint", "TRUE", &prefs_common.enable_rules_hint, P_BOOL},
151 {"bold_unread", "TRUE", &prefs_common.bold_unread, P_BOOL},
152
153 {"persist_qsearch_filter", "TRUE", &prefs_common.persist_qsearch_filter,
154 P_BOOL},
155
156 {"toolbar_style", "4", &prefs_common.toolbar_style, P_ENUM},
157 {"show_searchbar", "TRUE", &prefs_common.show_searchbar, P_BOOL},
158 {"show_statusbar", "TRUE", &prefs_common.show_statusbar, P_BOOL},
159
160 {"main_toolbar_setting", NULL, &prefs_common.main_toolbar_setting,
161 P_STRING},
162 {"compose_toolbar_setting", NULL,
163 &prefs_common.compose_toolbar_setting, P_STRING},
164
165 {"layout_type", "0", &prefs_common.layout_type, P_ENUM},
166
167 {"summary_col_show_mark", "TRUE",
168 &prefs_common.summary_col_visible[S_COL_MARK], P_BOOL},
169 {"summary_col_show_unread", "TRUE",
170 &prefs_common.summary_col_visible[S_COL_UNREAD], P_BOOL},
171 {"summary_col_show_mime", "TRUE",
172 &prefs_common.summary_col_visible[S_COL_MIME], P_BOOL},
173 {"summary_col_show_subject", "TRUE",
174 &prefs_common.summary_col_visible[S_COL_SUBJECT], P_BOOL},
175 {"summary_col_show_from", "TRUE",
176 &prefs_common.summary_col_visible[S_COL_FROM], P_BOOL},
177 {"summary_col_show_date", "TRUE",
178 &prefs_common.summary_col_visible[S_COL_DATE], P_BOOL},
179 {"summary_col_show_size", "TRUE",
180 &prefs_common.summary_col_visible[S_COL_SIZE], P_BOOL},
181 {"summary_col_show_number", "FALSE",
182 &prefs_common.summary_col_visible[S_COL_NUMBER], P_BOOL},
183 {"summary_col_show_to", "FALSE",
184 &prefs_common.summary_col_visible[S_COL_TO], P_BOOL},
185
186 {"summary_col_pos_mark", "0",
187 &prefs_common.summary_col_pos[S_COL_MARK], P_INT},
188 {"summary_col_pos_unread", "1",
189 &prefs_common.summary_col_pos[S_COL_UNREAD], P_INT},
190 {"summary_col_pos_mime", "2",
191 &prefs_common.summary_col_pos[S_COL_MIME], P_INT},
192 {"summary_col_pos_subject", "3",
193 &prefs_common.summary_col_pos[S_COL_SUBJECT], P_INT},
194 {"summary_col_pos_from", "4",
195 &prefs_common.summary_col_pos[S_COL_FROM], P_INT},
196 {"summary_col_pos_date", "5",
197 &prefs_common.summary_col_pos[S_COL_DATE], P_INT},
198 {"summary_col_pos_size", "6",
199 &prefs_common.summary_col_pos[S_COL_SIZE], P_INT},
200 {"summary_col_pos_number", "7",
201 &prefs_common.summary_col_pos[S_COL_NUMBER], P_INT},
202 {"summary_col_pos_to", "8",
203 &prefs_common.summary_col_pos[S_COL_TO], P_INT},
204
205 {"summary_sent_col_show_mark", "TRUE",
206 &prefs_common.summary_sent_col_visible[S_COL_MARK], P_BOOL},
207 {"summary_sent_col_show_unread", "TRUE",
208 &prefs_common.summary_sent_col_visible[S_COL_UNREAD], P_BOOL},
209 {"summary_sent_col_show_mime", "TRUE",
210 &prefs_common.summary_sent_col_visible[S_COL_MIME], P_BOOL},
211 {"summary_sent_col_show_subject", "TRUE",
212 &prefs_common.summary_sent_col_visible[S_COL_SUBJECT], P_BOOL},
213 {"summary_sent_col_show_from", "FALSE",
214 &prefs_common.summary_sent_col_visible[S_COL_FROM], P_BOOL},
215 {"summary_sent_col_show_date", "TRUE",
216 &prefs_common.summary_sent_col_visible[S_COL_DATE], P_BOOL},
217 {"summary_sent_col_show_size", "TRUE",
218 &prefs_common.summary_sent_col_visible[S_COL_SIZE], P_BOOL},
219 {"summary_sent_col_show_number", "FALSE",
220 &prefs_common.summary_sent_col_visible[S_COL_NUMBER], P_BOOL},
221 {"summary_sent_col_show_to", "TRUE",
222 &prefs_common.summary_sent_col_visible[S_COL_TO], P_BOOL},
223
224 {"summary_sent_col_pos_mark", "0",
225 &prefs_common.summary_sent_col_pos[S_COL_MARK], P_INT},
226 {"summary_sent_col_pos_unread", "1",
227 &prefs_common.summary_sent_col_pos[S_COL_UNREAD], P_INT},
228 {"summary_sent_col_pos_mime", "2",
229 &prefs_common.summary_sent_col_pos[S_COL_MIME], P_INT},
230 {"summary_sent_col_pos_subject", "3",
231 &prefs_common.summary_sent_col_pos[S_COL_SUBJECT], P_INT},
232 {"summary_sent_col_pos_from", "8",
233 &prefs_common.summary_sent_col_pos[S_COL_FROM], P_INT},
234 {"summary_sent_col_pos_date", "5",
235 &prefs_common.summary_sent_col_pos[S_COL_DATE], P_INT},
236 {"summary_sent_col_pos_size", "6",
237 &prefs_common.summary_sent_col_pos[S_COL_SIZE], P_INT},
238 {"summary_sent_col_pos_number", "7",
239 &prefs_common.summary_sent_col_pos[S_COL_NUMBER], P_INT},
240 {"summary_sent_col_pos_to", "4",
241 &prefs_common.summary_sent_col_pos[S_COL_TO], P_INT},
242
243 {"summary_col_size_mark", "10",
244 &prefs_common.summary_col_size[S_COL_MARK], P_INT},
245 {"summary_col_size_unread", "13",
246 &prefs_common.summary_col_size[S_COL_UNREAD], P_INT},
247 {"summary_col_size_mime", "10",
248 &prefs_common.summary_col_size[S_COL_MIME], P_INT},
249 {"summary_col_size_subject", "200",
250 &prefs_common.summary_col_size[S_COL_SUBJECT], P_INT},
251 {"summary_col_size_from", "120",
252 &prefs_common.summary_col_size[S_COL_FROM], P_INT},
253 {"summary_col_size_date", "118",
254 &prefs_common.summary_col_size[S_COL_DATE], P_INT},
255 {"summary_col_size_size", "45",
256 &prefs_common.summary_col_size[S_COL_SIZE], P_INT},
257 {"summary_col_size_number", "40",
258 &prefs_common.summary_col_size[S_COL_NUMBER], P_INT},
259 {"summary_col_size_to", "120",
260 &prefs_common.summary_col_size[S_COL_TO], P_INT},
261
262 /* Widget size */
263 {"folderwin_x", "16", &prefs_common.folderwin_x, P_INT},
264 {"folderwin_y", "16", &prefs_common.folderwin_y, P_INT},
265 {"folderview_width", "179", &prefs_common.folderview_width, P_INT},
266 {"folderview_height", "450", &prefs_common.folderview_height, P_INT},
267 {"folderview_visible", "TRUE", &prefs_common.folderview_visible,
268 P_BOOL},
269
270 {"folder_col_folder", "150", &prefs_common.folder_col_folder, P_INT},
271 {"folder_col_new", "32", &prefs_common.folder_col_new, P_INT},
272 {"folder_col_unread", "32", &prefs_common.folder_col_unread, P_INT},
273 {"folder_col_total", "32", &prefs_common.folder_col_total, P_INT},
274
275 {"summaryview_width", "600", &prefs_common.summaryview_width, P_INT},
276 {"summaryview_height", "180", &prefs_common.summaryview_height, P_INT},
277 {"summaryview_vwidth", "300", &prefs_common.summaryview_vwidth, P_INT},
278 {"summaryview_vheight", "600", &prefs_common.summaryview_vheight,
279 P_INT},
280
281 {"main_messagewin_x", "256", &prefs_common.main_msgwin_x, P_INT},
282 {"main_messagewin_y", "210", &prefs_common.main_msgwin_y, P_INT},
283 {"messageview_width", "600", &prefs_common.msgview_width, P_INT},
284 {"messageview_height", "300", &prefs_common.msgview_height, P_INT},
285 {"messageview_vwidth", "300", &prefs_common.msgview_vwidth, P_INT},
286 {"messageview_vheight", "600", &prefs_common.msgview_vheight, P_INT},
287 {"messageview_visible", "TRUE", &prefs_common.msgview_visible, P_BOOL},
288
289 {"mainview_x", "64", &prefs_common.mainview_x, P_INT},
290 {"mainview_y", "64", &prefs_common.mainview_y, P_INT},
291 {"mainview_width", "600", &prefs_common.mainview_width, P_INT},
292 {"mainview_height", "600", &prefs_common.mainview_height, P_INT},
293 {"mainwin_x", "64", &prefs_common.mainwin_x, P_INT},
294 {"mainwin_y", "64", &prefs_common.mainwin_y, P_INT},
295 {"mainwin_width", "800", &prefs_common.mainwin_width, P_INT},
296 {"mainwin_height", "600", &prefs_common.mainwin_height, P_INT},
297 {"messagewin_width", "600", &prefs_common.msgwin_width, P_INT},
298 {"messagewin_height", "540", &prefs_common.msgwin_height, P_INT},
299
300 {"mainwin_maximized", "FALSE", &prefs_common.mainwin_maximized, P_BOOL},
301
302 {"sourcewin_width", "600", &prefs_common.sourcewin_width, P_INT},
303 {"sourcewin_height", "500", &prefs_common.sourcewin_height, P_INT},
304
305 {"compose_x", "32", &prefs_common.compose_x, P_INT},
306 {"compose_y", "32", &prefs_common.compose_y, P_INT},
307 {"compose_width", "600", &prefs_common.compose_width, P_INT},
308 {"compose_height", "560", &prefs_common.compose_height, P_INT},
309
310 {"compose_maximized", "FALSE", &prefs_common.compose_maximized, P_BOOL},
311
312 {"addressbook_x", "32", &prefs_common.addressbook_x, P_INT},
313 {"addressbook_y", "32", &prefs_common.addressbook_y, P_INT},
314 {"addressbook_width", "620", &prefs_common.addressbook_width, P_INT},
315 {"addressbook_height", "360", &prefs_common.addressbook_height, P_INT},
316 {"addressbook_folder_width", "190", &prefs_common.addressbook_folder_width, P_INT},
317 {"addressbook_col_name", "164", &prefs_common.addressbook_col_name, P_INT},
318 {"addressbook_col_addr", "156", &prefs_common.addressbook_col_addr, P_INT},
319 {"addressbook_col_nickname", "120", &prefs_common.addressbook_col_nickname, P_INT},
320 {"addressbook_col_rem", "100", &prefs_common.addressbook_col_rem, P_INT},
321
322 /* Message */
323 {"enable_color", "TRUE", &prefs_common.enable_color, P_BOOL},
324
325 {"quote_level1_color", "179", &prefs_common.quote_level1_col, P_INT},
326 {"quote_level2_color", "179", &prefs_common.quote_level2_col, P_INT},
327 {"quote_level3_color", "179", &prefs_common.quote_level3_col, P_INT},
328 {"uri_color", "32512", &prefs_common.uri_col, P_INT},
329 {"signature_color", "0", &prefs_common.sig_col, P_USHORT},
330 {"recycle_quote_colors", "FALSE", &prefs_common.recycle_quote_colors,
331 P_BOOL},
332
333 {"convert_mb_alnum", "FALSE", &prefs_common.conv_mb_alnum, P_BOOL},
334 {"display_header_pane", "TRUE", &prefs_common.display_header_pane,
335 P_BOOL},
336 {"show_attach_tab", "FALSE", &prefs_common.show_attach_tab, P_BOOL},
337 {"show_attached_files_first", "TRUE",
338 &prefs_common.show_attached_files_first, P_BOOL},
339 {"attach_toolbutton_pos", "0", &prefs_common.attach_toolbutton_pos,
340 P_INT},
341 {"display_header", "TRUE", &prefs_common.display_header, P_BOOL},
342 {"render_html", "TRUE", &prefs_common.render_html, P_BOOL},
343 {"alt_prefer_html", "FALSE", &prefs_common.alt_prefer_html, P_BOOL},
344 {"html_only_as_attach", "FALSE", &prefs_common.html_only_as_attach,
345 P_BOOL},
346 {"line_space", "2", &prefs_common.line_space, P_INT},
347
348 {"textview_cursor_visible", "FALSE",
349 &prefs_common.textview_cursor_visible, P_BOOL},
350
351 {"enable_smooth_scroll", "FALSE", &prefs_common.enable_smooth_scroll,
352 P_BOOL},
353 {"scroll_step", "1", &prefs_common.scroll_step, P_INT},
354 {"scroll_half_page", "FALSE", &prefs_common.scroll_halfpage, P_BOOL},
355
356 {"resize_image", "TRUE", &prefs_common.resize_image, P_BOOL},
357 {"inline_image", "TRUE", &prefs_common.inline_image, P_BOOL},
358
359 /* Encoding */
360 {"default_encoding", NULL, &prefs_common.default_encoding, P_STRING},
361 {"outgoing_charset", NULL, &prefs_common.outgoing_charset, P_STRING},
362
363 {"show_other_header", "FALSE", &prefs_common.show_other_header, P_BOOL},
364
365 /* MIME viewer */
366 {"mime_image_viewer", NULL, &prefs_common.mime_image_viewer, P_STRING},
367 {"mime_audio_player", NULL, &prefs_common.mime_audio_player, P_STRING},
368#ifdef G_OS_WIN32
369 {"mime_open_command", "notepad '%s'", &prefs_common.mime_open_cmd,
370#else
371 {"mime_open_command", "gedit '%s'", &prefs_common.mime_open_cmd,
372#endif
373 P_STRING},
374 {"mime_command", NULL, &prefs_common.mime_cmd, P_STRING},
375
376 /* Junk mail */
377 {"enable_junk", "FALSE", &prefs_common.enable_junk, P_BOOL},
378#ifdef G_OS_WIN32
379 {"junk_learn_command", "sylfilter -j",
380 &prefs_common.junk_learncmd, P_STRING},
381 {"nojunk_learn_command", "sylfilter -c",
382 &prefs_common.nojunk_learncmd, P_STRING},
383 {"junk_classify_command", "sylfilter",
384 &prefs_common.junk_classify_cmd, P_STRING},
385#else
386 {"junk_learn_command", "bogofilter -N -s -I",
387 &prefs_common.junk_learncmd, P_STRING},
388 {"nojunk_learn_command", "bogofilter -n -S -I",
389 &prefs_common.nojunk_learncmd, P_STRING},
390 {"junk_classify_command", "bogofilter -I",
391 &prefs_common.junk_classify_cmd, P_STRING},
392#endif
393 {"junk_folder", NULL, &prefs_common.junk_folder, P_STRING},
394 {"filter_junk_on_receive", "FALSE", &prefs_common.filter_junk_on_recv,
395 P_BOOL},
396 {"filter_junk_before", "FALSE", &prefs_common.filter_junk_before,
397 P_BOOL},
398 {"delete_junk_on_receive", "TRUE", &prefs_common.delete_junk_on_recv,
399 P_BOOL},
400 {"nofilter_junk_sender_in_book", "TRUE",
401 &prefs_common.nofilter_junk_sender_in_book, P_BOOL},
402 {"mark_junk_as_read", "FALSE", &prefs_common.mark_junk_as_read, P_BOOL},
403
404 /* Privacy */
405 {"auto_check_signatures", "TRUE", &prefs_common.auto_check_signatures,
406 P_BOOL},
407 {"gpg_signature_popup", "FALSE", &prefs_common.gpg_signature_popup,
408 P_BOOL},
409 {"store_passphrase", "FALSE", &prefs_common.store_passphrase, P_BOOL},
410 {"store_passphrase_timeout", "0",
411 &prefs_common.store_passphrase_timeout, P_INT},
412 {"passphrase_grab", "FALSE", &prefs_common.passphrase_grab, P_BOOL},
413#ifdef G_OS_WIN32
414 {"show_gpg_warning", "FALSE", &prefs_common.gpg_warning, P_BOOL},
415#else
416 {"show_gpg_warning", "TRUE", &prefs_common.gpg_warning, P_BOOL},
417#endif
418
419 /* Interface */
420 {"separate_folder", "FALSE", &prefs_common.sep_folder, P_BOOL},
421 {"separate_message", "FALSE", &prefs_common.sep_msg, P_BOOL},
422
423 {"always_show_message_when_selected", "TRUE",
424 &prefs_common.always_show_msg, P_BOOL},
425 {"open_unread_on_enter", "FALSE", &prefs_common.open_unread_on_enter,
426 P_BOOL},
427 {"always_mark_read_on_show_msg", "FALSE",
428 &prefs_common.always_mark_read_on_show_msg, P_BOOL},
429 {"remember_last_selected", "FALSE",
430 &prefs_common.remember_last_selected, P_BOOL},
431 {"mark_as_read_on_new_window", "FALSE",
432 &prefs_common.mark_as_read_on_new_window, P_BOOL},
433 {"open_inbox_on_inc", "FALSE", &prefs_common.open_inbox_on_inc, P_BOOL},
434 {"open_inbox_on_startup", "FALSE", &prefs_common.open_inbox_on_startup,
435 P_BOOL},
436 {"change_account_on_folder_selection", "TRUE",
437 &prefs_common.change_account_on_folder_sel, P_BOOL},
438 {"immediate_execution", "TRUE", &prefs_common.immediate_exec, P_BOOL},
439
440#ifdef G_OS_WIN32
441 {"comply_gnome_hig", "FALSE", &prefs_common.comply_gnome_hig, P_BOOL},
442#else
443 {"comply_gnome_hig", "TRUE", &prefs_common.comply_gnome_hig, P_BOOL},
444#endif
445
446 {"show_trayicon", "TRUE", &prefs_common.show_trayicon, P_BOOL},
447 {"minimize_to_tray", "FALSE", &prefs_common.minimize_to_tray, P_BOOL},
448#ifdef G_OS_WIN32
449 {"toggle_window_on_trayicon_click", "FALSE",
450#else
451 {"toggle_window_on_trayicon_click", "TRUE",
452#endif
453 &prefs_common.toggle_window_on_trayicon_click, P_BOOL},
454
455 /* Other */
456 {"receive_dialog_mode", "1", &prefs_common.recv_dialog_mode, P_ENUM},
457 {"no_receive_error_panel", "FALSE", &prefs_common.no_recv_err_panel,
458 P_BOOL},
459 {"close_receive_dialog", "TRUE", &prefs_common.close_recv_dialog,
460 P_BOOL},
461
462 {"add_address_by_click", "FALSE", &prefs_common.add_address_by_click,
463 P_BOOL},
464 {"enable_address_completion", "TRUE",
465 &prefs_common.enable_address_completion, P_BOOL},
466 {"fullauto_completion_mode", "TRUE",
467 &prefs_common.fullauto_completion_mode, P_BOOL},
468 {"always_add_address_only", "FALSE",
469 &prefs_common.always_add_address_only, P_BOOL},
470
471 {"confirm_on_exit", "FALSE", &prefs_common.confirm_on_exit, P_BOOL},
472 {"clean_trash_on_exit", "FALSE", &prefs_common.clean_on_exit, P_BOOL},
473 {"ask_on_cleaning", "TRUE", &prefs_common.ask_on_clean, P_BOOL},
474 {"warn_queued_on_exit", "TRUE", &prefs_common.warn_queued_on_exit,
475 P_BOOL},
476
477 {"logwindow_line_limit", "1000", &prefs_common.logwin_line_limit,
478 P_INT},
479
480 {"online_mode", "TRUE", &prefs_common.online_mode, P_BOOL},
481 {"startup_online_mode", "1", &prefs_common.startup_online_mode, P_INT},
482
483 /* External commands */
484#ifdef G_OS_WIN32
485 {"uri_open_command", NULL, &prefs_common.uri_cmd,
486#else
487 {"uri_open_command", DEFAULT_BROWSER_CMD, &prefs_common.uri_cmd,
488#endif
489 P_STRING},
490#ifdef G_OS_WIN32
491 {"ext_editor_command", "notepad '%s'", &prefs_common.ext_editor_cmd,
492#else
493 {"ext_editor_command", "gedit %s", &prefs_common.ext_editor_cmd,
494#endif
495 P_STRING},
496
497 {"use_print_command", "FALSE", &prefs_common.use_print_cmd, P_BOOL},
498#ifdef G_OS_WIN32
499 {"print_command", NULL, &prefs_common.print_cmd, P_STRING},
500#else
501 {"print_command", "lpr %s", &prefs_common.print_cmd, P_STRING},
502#endif
503
504 {"use_ext_inc", "FALSE", &prefs_common.use_extinc, P_BOOL},
505 {"ext_inc_path", DEFAULT_INC_PATH, &prefs_common.extinc_cmd, P_STRING},
506 {"use_ext_sendmail", "FALSE", &prefs_common.use_extsend, P_BOOL},
507 {"ext_sendmail_cmd", DEFAULT_SENDMAIL_CMD, &prefs_common.extsend_cmd,
508 P_STRING},
509
510 /* Update check */
511 {"auto_update_check", "TRUE", &prefs_common.auto_update_check, P_BOOL},
512 {"use_http_proxy", "FALSE", &prefs_common.use_http_proxy, P_BOOL},
513 {"http_proxy_host", NULL, &prefs_common.http_proxy_host, P_STRING},
514
515 /* Advanced */
516 {"strict_cache_check", "FALSE", &prefs_common.strict_cache_check,
517 P_BOOL},
518 {"io_timeout_secs", "60", &prefs_common.io_timeout_secs, P_INT},
519
520 /* File selector */
521 {"filesel_prev_open_dir", NULL, &prefs_common.prev_open_dir, P_STRING},
522 {"filesel_prev_save_dir", NULL, &prefs_common.prev_save_dir, P_STRING},
523 {"filesel_prev_folder_dir", NULL, &prefs_common.prev_folder_dir, P_STRING},
524 {"filesel_save_file_type", "0", &prefs_common.save_file_type, P_INT},
525
526 {NULL, NULL, NULL, P_OTHER}
527};
528
529
530PrefsCommon *prefs_common_get(void)
531{
532 return &prefs_common;
533}
534
535PrefParam *prefs_common_get_params(void)
536{
537 return param;
538}
539
540void prefs_common_read_config(void)
541{
542 FILE *fp;
543 gchar *path;
544 gchar buf[PREFSBUFSIZE];
545
546 path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
547
548 prefs_read_config(param, "Common", path, NULL);
549
550 g_free(path);
551
552 //prefs_common.online_mode = TRUE;
553
554 path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMAND_HISTORY,
555 NULL);
556 if ((fp = g_fopen(path, "rb")) == NULL) {
557 if (ENOENT != errno) FILE_OP_ERROR(path, "fopen");
558 g_free(path);
559 return;
560 }
561 g_free(path);
562 while (fgets(buf, sizeof(buf), fp) != NULL) {
563 g_strstrip(buf);
564 if (buf[0] == '\0') continue;
565 prefs_common.mime_open_cmd_history =
566 add_history(prefs_common.mime_open_cmd_history, buf);
567 }
568 fclose(fp);
569
570 prefs_common.mime_open_cmd_history =
571 g_list_reverse(prefs_common.mime_open_cmd_history);
572}
573
574void prefs_common_write_config(void)
575{
576 GList *cur;
577 FILE *fp;
578 gchar *path;
579
580 prefs_write_config(param, "Common", COMMON_RC);
581
582 path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMAND_HISTORY,
583 NULL);
584 if ((fp = g_fopen(path, "wb")) == NULL) {
585 FILE_OP_ERROR(path, "fopen");
586 g_free(path);
587 return;
588 }
589
590 for (cur = prefs_common.mime_open_cmd_history;
591 cur != NULL; cur = cur->next) {
592 fputs((gchar *)cur->data, fp);
593 fputc('\n', fp);
594 }
595
596 fclose(fp);
597 g_free(path);
598}
599
600void prefs_common_junk_filter_list_set(void)
601{
602}
603
604void prefs_common_junk_folder_rename_path(const gchar *old_path,
605 const gchar *new_path)
606{
607 gint len;
608 gchar *base;
609 gchar *dest_path;
610
611 g_return_if_fail(old_path != NULL);
612 g_return_if_fail(new_path != NULL);
613
614 if (!prefs_common.junk_folder)
615 return;
616
617 len = strlen(old_path);
618
619 if (!strncmp(old_path, prefs_common.junk_folder, len)) {
620 base = prefs_common.junk_folder + len;
621 if (*base != '/' && *base != '\0')
622 return;
623 while (*base == '/') base++;
624 if (*base == '\0')
625 dest_path = g_strdup(new_path);
626 else
627 dest_path = g_strconcat(new_path, "/", base, NULL);
628 debug_print("prefs_common_junk_folder_rename_path(): "
629 "renaming %s -> %s\n", prefs_common.junk_folder,
630 dest_path);
631 g_free(prefs_common.junk_folder);
632 prefs_common.junk_folder = dest_path;
633 }
634}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2017 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __PREFS_COMMON_H__
21#define __PREFS_COMMON_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28
29typedef struct _PrefsCommon PrefsCommon;
30
31#include "enums.h"
32#include "prefs.h"
33
34typedef enum {
35 RECV_DIALOG_ALWAYS,
36 RECV_DIALOG_MANUAL,
37 RECV_DIALOG_NEVER
38} RecvDialogMode;
39
40typedef enum {
41 CTE_AUTO,
42 CTE_BASE64,
43 CTE_QUOTED_PRINTABLE,
44 CTE_8BIT
45} TransferEncodingMethod;
46
47typedef enum {
48 FENC_MIME,
49 FENC_RFC2231,
50 FENC_NONE
51} MIMEFilenameEncodingMethod;
52
53struct _PrefsCommon
54{
55 /* Receive */
56 gboolean scan_all_after_inc;
57 gboolean autochk_newmail;
58 gint autochk_itv;
59 gboolean chk_on_startup;
60 gboolean enable_newmsg_notify;
61 gchar *newmsg_notify_cmd;
62
63 gboolean inc_local;
64 gboolean filter_on_inc;
65 gchar *spool_path;
66
67 /* Send */
68 gboolean savemsg;
69 gboolean filter_sent;
70 TransferEncodingMethod encoding_method;
71 MIMEFilenameEncodingMethod mime_fencoding_method;
72
73 gboolean check_attach;
74 gchar *check_attach_str;
75 gboolean check_recipients;
76 gchar *check_recp_exclude;
77
78 gboolean allow_jisx0201_kana;
79
80 /* Compose */
81 gboolean auto_sig;
82 gchar *sig_sep;
83 gint undolevels;
84 gint linewrap_len;
85 gboolean linewrap_quote;
86 gboolean autowrap;
87 gboolean auto_exteditor;
88 gboolean enable_autosave;
89 gint autosave_itv;
90 gboolean reply_account_autosel;
91 gboolean default_reply_list;
92 gboolean inherit_recipient_on_self_reply;
93
94 gboolean show_ruler;
95
96 /* Quote */
97 gboolean reply_with_quote;
98 gchar *quotemark;
99 gchar *quotefmt;
100 gchar *fw_quotemark;
101 gchar *fw_quotefmt;
102
103 /* Spelling */
104 gboolean check_spell;
105 gchar *spell_lang;
106
107 /* Display */
108 gchar *textfont;
109
110 gboolean trans_hdr;
111 gboolean display_folder_unread;
112 gboolean display_folder_num_columns;
113 gint ng_abbrev_len;
114
115 gboolean swap_from;
116 gboolean expand_thread;
117 gchar *date_format;
118
119 gboolean enable_rules_hint;
120 gboolean bold_unread;
121
122 gboolean persist_qsearch_filter;
123
124 ToolbarStyle toolbar_style;
125 gboolean show_searchbar;
126 gboolean show_statusbar;
127
128 gchar *main_toolbar_setting;
129 gchar *compose_toolbar_setting;
130
131 LayoutType layout_type;
132
133 /* Summary columns visibility, position and size */
134 gboolean summary_col_visible[N_SUMMARY_COLS];
135 gint summary_col_pos[N_SUMMARY_COLS];
136 gboolean summary_sent_col_visible[N_SUMMARY_COLS];
137 gint summary_sent_col_pos[N_SUMMARY_COLS];
138 gint summary_col_size[N_SUMMARY_COLS];
139
140 /* Widget visibility, position and size */
141 gint folderwin_x;
142 gint folderwin_y;
143 gint folderview_width;
144 gint folderview_height;
145 gboolean folderview_visible;
146
147 gint folder_col_folder;
148 gint folder_col_new;
149 gint folder_col_unread;
150 gint folder_col_total;
151
152 gint summaryview_width;
153 gint summaryview_height;
154 gint summaryview_vwidth;
155 gint summaryview_vheight;
156
157 gint main_msgwin_x;
158 gint main_msgwin_y;
159 gint msgview_width;
160 gint msgview_height;
161 gint msgview_vwidth;
162 gint msgview_vheight;
163 gboolean msgview_visible;
164
165 gint mainview_x;
166 gint mainview_y;
167 gint mainview_width;
168 gint mainview_height;
169 gint mainwin_x;
170 gint mainwin_y;
171 gint mainwin_width;
172 gint mainwin_height;
173
174 gint msgwin_width;
175 gint msgwin_height;
176
177 gboolean mainwin_maximized;
178
179 gint sourcewin_width;
180 gint sourcewin_height;
181
182 gint compose_x;
183 gint compose_y;
184 gint compose_width;
185 gint compose_height;
186
187 gboolean compose_maximized;
188
189 gint addressbook_x;
190 gint addressbook_y;
191 gint addressbook_width;
192 gint addressbook_height;
193
194 /* Message */
195 gboolean enable_color;
196 gint quote_level1_col;
197 gint quote_level2_col;
198 gint quote_level3_col;
199 gint uri_col;
200 gushort sig_col;
201 gboolean recycle_quote_colors;
202 gboolean conv_mb_alnum;
203 gboolean display_header_pane;
204 gboolean display_header;
205 gint line_space;
206 gboolean render_html;
207 gboolean html_only_as_attach;
208 gboolean textview_cursor_visible;
209 gboolean enable_smooth_scroll;
210 gint scroll_step;
211 gboolean scroll_halfpage;
212
213 gboolean resize_image;
214 gboolean inline_image;
215
216 /* Encoding */
217 gchar *force_charset;
218 gchar *default_encoding;
219 gchar *outgoing_charset;
220
221 gboolean show_other_header;
222 GSList *disphdr_list;
223
224 /* MIME viewer */
225 gchar *mime_image_viewer;
226 gchar *mime_audio_player;
227 gchar *mime_open_cmd;
228 gchar *mime_cmd;
229
230 GList *mime_open_cmd_history;
231
232 /* Junk Mail */
233 gboolean enable_junk;
234 gchar *junk_learncmd;
235 gchar *nojunk_learncmd;
236 gchar *junk_classify_cmd;
237 gchar *junk_folder;
238 gboolean filter_junk_on_recv;
239 gboolean filter_junk_before;
240 gboolean delete_junk_on_recv;
241 gboolean mark_junk_as_read;
242
243 /* Privacy */
244 gboolean auto_check_signatures;
245 gboolean gpg_signature_popup;
246 gboolean store_passphrase;
247 gint store_passphrase_timeout;
248 gboolean passphrase_grab;
249 gboolean gpg_warning;
250
251 /* Interface */
252 gboolean sep_folder;
253 gboolean sep_msg;
254 gboolean always_show_msg;
255 gboolean open_unread_on_enter;
256 gboolean remember_last_selected;
257 gboolean mark_as_read_on_new_window;
258 gboolean open_inbox_on_inc;
259 gboolean open_inbox_on_startup;
260 gboolean immediate_exec;
261 gboolean comply_gnome_hig;
262 gboolean show_trayicon;
263 gboolean minimize_to_tray;
264 gboolean toggle_window_on_trayicon_click;
265
266 /* Other */
267 RecvDialogMode recv_dialog_mode;
268 gboolean no_recv_err_panel;
269 gboolean close_recv_dialog;
270
271 gboolean add_address_by_click;
272
273 gboolean confirm_on_exit;
274 gboolean clean_on_exit;
275 gboolean ask_on_clean;
276 gboolean warn_queued_on_exit;
277
278 gint logwin_line_limit;
279
280 /* External commands */
281 gchar *uri_cmd;
282 gchar *ext_editor_cmd;
283
284 gboolean use_print_cmd;
285 gchar *print_cmd;
286
287 gboolean use_extinc;
288 gchar *extinc_cmd;
289 gboolean use_extsend;
290 gchar *extsend_cmd;
291
292 /* Update check */
293 gboolean auto_update_check;
294 gboolean use_http_proxy;
295 gchar *http_proxy_host;
296
297 /* Advanced */
298 gboolean strict_cache_check;
299 gint io_timeout_secs;
300
301 /* Filtering */
302 GSList *fltlist;
303
304 /* deprecated: do not use */
305 GSList *junk_fltlist;
306 GSList *manual_junk_fltlist;
307
308 /* Actions */
309 GSList *actions_list;
310
311 /* Online / Offline */
312 gboolean online_mode;
313
314 /* Append new members here */
315 gboolean folder_col_visible[4]; /* Display */
316 gboolean reply_address_only; /* Compose */
317 gboolean recipients_autoreg; /* Send */
318 gboolean enable_address_completion; /* Compose */
319 gboolean fullauto_completion_mode; /* Compose */
320
321 gchar *user_agent_str;
322
323 gboolean change_account_on_folder_sel; /* Interface */
324 gboolean always_mark_read_on_show_msg; /* Interface */
325
326 gboolean always_add_address_only; /* Compose */
327 gboolean show_send_dialog; /* Send */
328
329 gint addressbook_folder_width;
330 gint addressbook_col_name;
331 gint addressbook_col_addr;
332 gint addressbook_col_rem;
333
334 gchar *prev_open_dir;
335 gchar *prev_save_dir;
336 gchar *prev_folder_dir;
337
338 gboolean enable_newmsg_notify_sound; /* Receive */
339 gchar *newmsg_notify_sound; /* Receive */
340
341 gboolean show_attach_tab; /* Message - Attachment */
342 gboolean show_attached_files_first; /* Message - Attachment */
343 gint attach_toolbutton_pos; /* Message - Attachment */
344
345 gboolean enable_newmsg_notify_window; /* Receive */
346
347 gboolean nofilter_junk_sender_in_book; /* Junk Mail */
348
349 gboolean alt_prefer_html; /* Message */
350
351 gint save_file_type;
352
353 gint notify_window_period; /* Receive */
354
355 gint startup_online_mode; /* Online */
356
357 gint addressbook_col_nickname;
358};
359
360extern PrefsCommon prefs_common;
361
362PrefsCommon *prefs_common_get (void);
363
364PrefParam *prefs_common_get_params (void);
365
366void prefs_common_read_config (void);
367void prefs_common_write_config (void);
368
369/* deprecated */
370void prefs_common_junk_filter_list_set (void);
371
372void prefs_common_junk_folder_rename_path (const gchar *old_path,
373 const gchar *new_path);
374
375#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include <glib.h>
25#include <glib/gi18n.h>
26#include <stdio.h>
27#include <string.h>
28#include <stdlib.h>
29#include <time.h>
30#include <sys/stat.h>
31
32#include "procheader.h"
33#include "procmsg.h"
34#include "codeconv.h"
35#include "displayheader.h"
36#include "prefs_common.h"
37#include "utils.h"
38
39#define BUFFSIZE 8192
40
41gint procheader_get_one_field(gchar *buf, size_t len, FILE *fp,
42 HeaderEntry hentry[])
43{
44 gint nexthead;
45 gint hnum = 0;
46 HeaderEntry *hp = NULL;
47
48 if (hentry != NULL) {
49 /* skip non-required headers */
50 do {
51 do {
52 if (fgets(buf, len, fp) == NULL)
53 return -1;
54 if (buf[0] == '\r' || buf[0] == '\n')
55 return -1;
56 } while (buf[0] == ' ' || buf[0] == '\t');
57
58 for (hp = hentry, hnum = 0; hp->name != NULL;
59 hp++, hnum++) {
60 if (!g_ascii_strncasecmp(hp->name, buf,
61 strlen(hp->name)))
62 break;
63 }
64 } while (hp->name == NULL);
65 } else {
66 if (fgets(buf, len, fp) == NULL) return -1;
67 if (buf[0] == '\r' || buf[0] == '\n') return -1;
68 }
69
70 /* unfold the specified folded line */
71 if (hp && hp->unfold) {
72 gboolean folded = FALSE;
73 gchar *bufp = buf + strlen(buf);
74
75 for (; bufp > buf &&
76 (*(bufp - 1) == '\n' || *(bufp - 1) == '\r');
77 bufp--)
78 *(bufp - 1) = '\0';
79
80 while (1) {
81 nexthead = fgetc(fp);
82
83 /* folded */
84 if (nexthead == ' ' || nexthead == '\t')
85 folded = TRUE;
86 else if (nexthead == EOF)
87 break;
88 else if (folded == TRUE) {
89 if ((len - (bufp - buf)) <= 2) break;
90
91 if (nexthead == '\n') {
92 folded = FALSE;
93 continue;
94 }
95
96 /* replace return code on the tail end
97 with space */
98 *bufp++ = ' ';
99 *bufp++ = nexthead;
100 *bufp = '\0';
101
102 /* concatenate next line */
103 if (fgets(bufp, len - (bufp - buf), fp)
104 == NULL) break;
105 bufp += strlen(bufp);
106
107 for (; bufp > buf &&
108 (*(bufp - 1) == '\n' || *(bufp - 1) == '\r');
109 bufp--)
110 *(bufp - 1) = '\0';
111
112 folded = FALSE;
113 } else {
114 ungetc(nexthead, fp);
115 break;
116 }
117 }
118
119 return hnum;
120 }
121
122 while (1) {
123 nexthead = fgetc(fp);
124 if (nexthead == ' ' || nexthead == '\t') {
125 size_t buflen = strlen(buf);
126
127 /* concatenate next line */
128 if ((len - buflen) > 2) {
129 gchar *p = buf + buflen;
130
131 *p++ = nexthead;
132 *p = '\0';
133 buflen++;
134 if (fgets(p, len - buflen, fp) == NULL)
135 break;
136 } else
137 break;
138 } else {
139 if (nexthead != EOF)
140 ungetc(nexthead, fp);
141 break;
142 }
143 }
144
145 /* remove trailing return code */
146 strretchomp(buf);
147
148 return hnum;
149}
150
151gchar *procheader_get_unfolded_line(gchar *buf, size_t len, FILE *fp)
152{
153 gboolean folded = FALSE;
154 gint nexthead;
155 gchar *bufp;
156
157 if (fgets(buf, len, fp) == NULL) return NULL;
158 if (buf[0] == '\r' || buf[0] == '\n') return NULL;
159 bufp = buf + strlen(buf);
160
161 for (; bufp > buf &&
162 (*(bufp - 1) == '\n' || *(bufp - 1) == '\r');
163 bufp--)
164 *(bufp - 1) = '\0';
165
166 while (1) {
167 nexthead = fgetc(fp);
168
169 /* folded */
170 if (nexthead == ' ' || nexthead == '\t')
171 folded = TRUE;
172 else if (nexthead == EOF)
173 break;
174 else if (folded == TRUE) {
175 if ((len - (bufp - buf)) <= 2) break;
176
177 if (nexthead == '\n') {
178 folded = FALSE;
179 continue;
180 }
181
182 /* replace return code on the tail end
183 with space */
184 *bufp++ = ' ';
185 *bufp++ = nexthead;
186 *bufp = '\0';
187
188 /* concatenate next line */
189 if (fgets(bufp, len - (bufp - buf), fp)
190 == NULL) break;
191 bufp += strlen(bufp);
192
193 for (; bufp > buf &&
194 (*(bufp - 1) == '\n' || *(bufp - 1) == '\r');
195 bufp--)
196 *(bufp - 1) = '\0';
197
198 folded = FALSE;
199 } else {
200 ungetc(nexthead, fp);
201 break;
202 }
203 }
204
205 /* remove trailing return code */
206 strretchomp(buf);
207
208 return buf;
209}
210
211GSList *procheader_get_header_list_from_file(const gchar *file)
212{
213 FILE *fp;
214 GSList *hlist;
215
216 if ((fp = g_fopen(file, "rb")) == NULL) {
217 FILE_OP_ERROR(file, "procheader_get_header_list_from_file: fopen");
218 return NULL;
219 }
220
221 hlist = procheader_get_header_list(fp);
222
223 fclose(fp);
224 return hlist;
225}
226
227GSList *procheader_get_header_list(FILE *fp)
228{
229 gchar buf[BUFFSIZE];
230 gchar *p;
231 GSList *hlist = NULL;
232 Header *header;
233
234 g_return_val_if_fail(fp != NULL, NULL);
235
236 while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) {
237 if (*buf == ':') continue;
238 for (p = buf; *p && *p != ' '; p++) {
239 if (*p == ':') {
240 header = g_new(Header, 1);
241 header->name = g_strndup(buf, p - buf);
242 p++;
243 while (*p == ' ' || *p == '\t') p++;
244 header->body = conv_unmime_header(p, NULL);
245
246 hlist = g_slist_append(hlist, header);
247 break;
248 }
249 }
250 }
251
252 return hlist;
253}
254
255GSList *procheader_get_header_list_from_msginfo(MsgInfo *msginfo)
256{
257 GSList *hlist = NULL;
258
259 g_return_val_if_fail(msginfo != NULL, NULL);
260
261 if (msginfo->subject)
262 hlist = procheader_add_header_list(hlist, "Subject",
263 msginfo->subject);
264 if (msginfo->from)
265 hlist = procheader_add_header_list(hlist, "From",
266 msginfo->from);
267 if (msginfo->to)
268 hlist = procheader_add_header_list(hlist, "To", msginfo->to);
269 if (msginfo->cc)
270 hlist = procheader_add_header_list(hlist, "Cc", msginfo->cc);
271 if (msginfo->newsgroups)
272 hlist = procheader_add_header_list(hlist, "Newsgroups",
273 msginfo->newsgroups);
274 if (msginfo->date)
275 hlist = procheader_add_header_list(hlist, "Date",
276 msginfo->date);
277
278 return hlist;
279}
280
281GSList *procheader_add_header_list(GSList *hlist, const gchar *header_name,
282 const gchar *body)
283{
284 Header *header;
285
286 g_return_val_if_fail(header_name != NULL, hlist);
287
288 header = g_new(Header, 1);
289 header->name = g_strdup(header_name);
290 header->body = g_strdup(body);
291
292 return g_slist_append(hlist, header);
293}
294
295GSList *procheader_copy_header_list(GSList *hlist)
296{
297 GSList *newlist = NULL, *cur;
298
299 for (cur = hlist; cur != NULL; cur = cur->next) {
300 Header *header = (Header *)cur->data;
301 newlist = procheader_add_header_list(newlist, header->name,
302 header->body);
303 }
304
305 return newlist;
306}
307
308GSList *procheader_merge_header_list(GSList *hlist1, GSList *hlist2)
309{
310 GSList *cur;
311
312 for (cur = hlist2; cur != NULL; cur = cur->next) {
313 Header *header = (Header *)cur->data;
314 if (procheader_find_header_list(hlist1, header->name) < 0)
315 hlist1 = g_slist_append(hlist1, header);
316 }
317
318 return hlist1;
319}
320
321GSList *procheader_merge_header_list_dup(GSList *hlist1, GSList *hlist2)
322{
323 GSList *list, *cur;
324
325 list = procheader_copy_header_list(hlist1);
326
327 for (cur = hlist2; cur != NULL; cur = cur->next) {
328 Header *header = (Header *)cur->data;
329 if (procheader_find_header_list(list, header->name) < 0)
330 list = procheader_add_header_list(list, header->name,
331 header->body);
332 }
333
334 return list;
335}
336
337gint procheader_find_header_list(GSList *hlist, const gchar *header_name)
338{
339 GSList *cur;
340 gint index = 0;
341 Header *header;
342
343 g_return_val_if_fail(header_name != NULL, -1);
344
345 for (cur = hlist; cur != NULL; cur = cur->next, index++) {
346 header = (Header *)cur->data;
347 if (g_ascii_strcasecmp(header->name, header_name) == 0)
348 return index;
349 }
350
351 return -1;
352}
353
354GPtrArray *procheader_get_header_array(FILE *fp, const gchar *encoding)
355{
356 gchar buf[BUFFSIZE];
357 gchar *p;
358 GPtrArray *headers;
359 Header *header;
360
361 g_return_val_if_fail(fp != NULL, NULL);
362
363 headers = g_ptr_array_new();
364
365 while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) {
366 if (*buf == ':') continue;
367 for (p = buf; *p && *p != ' '; p++) {
368 if (*p == ':') {
369 header = g_new(Header, 1);
370 header->name = g_strndup(buf, p - buf);
371 p++;
372 while (*p == ' ' || *p == '\t') p++;
373 header->body = conv_unmime_header(p, encoding);
374
375 g_ptr_array_add(headers, header);
376 break;
377 }
378 }
379 }
380
381 return headers;
382}
383
384GPtrArray *procheader_get_header_array_asis(FILE *fp, const gchar *encoding)
385{
386 gchar buf[BUFFSIZE];
387 gchar *p;
388 GPtrArray *headers;
389 Header *header;
390
391 g_return_val_if_fail(fp != NULL, NULL);
392
393 headers = g_ptr_array_new();
394
395 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
396 if (*buf == ':') continue;
397 for (p = buf; *p && *p != ' '; p++) {
398 if (*p == ':') {
399 header = g_new(Header, 1);
400 header->name = g_strndup(buf, p - buf);
401 p++;
402 header->body = conv_unmime_header(p, encoding);
403
404 g_ptr_array_add(headers, header);
405 break;
406 }
407 }
408 }
409
410 return headers;
411}
412
413GPtrArray *procheader_get_header_array_for_display(FILE *fp,
414 const gchar *encoding)
415{
416 GPtrArray *headers, *sorted_headers;
417 GSList *disphdr_list;
418 Header *header;
419 gint i;
420
421 g_return_val_if_fail(fp != NULL, NULL);
422
423 headers = procheader_get_header_array_asis(fp, encoding);
424
425 sorted_headers = g_ptr_array_new();
426
427 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
428 disphdr_list = disphdr_list->next) {
429 DisplayHeaderProp *dp =
430 (DisplayHeaderProp *)disphdr_list->data;
431
432 for (i = 0; i < headers->len; i++) {
433 header = g_ptr_array_index(headers, i);
434
435 if (!g_ascii_strcasecmp(header->name, dp->name)) {
436 if (dp->hidden)
437 procheader_header_free(header);
438 else
439 g_ptr_array_add(sorted_headers, header);
440
441 g_ptr_array_remove_index(headers, i);
442 i--;
443 }
444 }
445 }
446
447 if (prefs_common.show_other_header) {
448 for (i = 0; i < headers->len; i++) {
449 header = g_ptr_array_index(headers, i);
450 g_ptr_array_add(sorted_headers, header);
451 }
452 g_ptr_array_free(headers, TRUE);
453 } else
454 procheader_header_array_destroy(headers);
455
456 return sorted_headers;
457}
458
459void procheader_header_list_destroy(GSList *hlist)
460{
461 Header *header;
462
463 while (hlist != NULL) {
464 header = hlist->data;
465 procheader_header_free(header);
466 hlist = g_slist_remove(hlist, header);
467 }
468}
469
470void procheader_header_array_destroy(GPtrArray *harray)
471{
472 gint i;
473 Header *header;
474
475 for (i = 0; i < harray->len; i++) {
476 header = g_ptr_array_index(harray, i);
477 procheader_header_free(header);
478 }
479
480 g_ptr_array_free(harray, TRUE);
481}
482
483void procheader_header_free(Header *header)
484{
485 if (!header) return;
486
487 g_free(header->name);
488 g_free(header->body);
489 g_free(header);
490}
491
492void procheader_get_header_fields(FILE *fp, HeaderEntry hentry[])
493{
494 gchar buf[BUFFSIZE];
495 HeaderEntry *hp;
496 gint hnum;
497 gchar *p;
498
499 if (hentry == NULL) return;
500
501 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
502 != -1) {
503 hp = hentry + hnum;
504
505 p = buf + strlen(hp->name);
506 while (*p == ' ' || *p == '\t') p++;
507
508 if (hp->body == NULL)
509 hp->body = g_strdup(p);
510 else if (!g_ascii_strcasecmp(hp->name, "To:") ||
511 !g_ascii_strcasecmp(hp->name, "Cc:")) {
512 gchar *tp = hp->body;
513 hp->body = g_strconcat(tp, ", ", p, NULL);
514 g_free(tp);
515 }
516 }
517}
518
519MsgInfo *procheader_parse_file(const gchar *file, MsgFlags flags,
520 gboolean full)
521{
522 GStatBuf s;
523 FILE *fp;
524 MsgInfo *msginfo;
525
526 if (g_stat(file, &s) < 0) {
527 FILE_OP_ERROR(file, "stat");
528 return NULL;
529 }
530 if (!S_ISREG(s.st_mode))
531 return NULL;
532
533 if ((fp = g_fopen(file, "rb")) == NULL) {
534 FILE_OP_ERROR(file, "procheader_parse_file: fopen");
535 return NULL;
536 }
537
538 msginfo = procheader_parse_stream(fp, flags, full);
539 fclose(fp);
540
541 if (msginfo) {
542 msginfo->size = s.st_size;
543 msginfo->mtime = s.st_mtime;
544 }
545
546 return msginfo;
547}
548
549MsgInfo *procheader_parse_str(const gchar *str, MsgFlags flags, gboolean full)
550{
551 FILE *fp;
552 MsgInfo *msginfo;
553
554 if ((fp = str_open_as_stream(str)) == NULL)
555 return NULL;
556
557 msginfo = procheader_parse_stream(fp, flags, full);
558 fclose(fp);
559 return msginfo;
560}
561
562enum
563{
564 H_DATE = 0,
565 H_FROM = 1,
566 H_TO = 2,
567 H_NEWSGROUPS = 3,
568 H_SUBJECT = 4,
569 H_MSG_ID = 5,
570 H_REFERENCES = 6,
571 H_IN_REPLY_TO = 7,
572 H_CONTENT_TYPE = 8,
573 H_SEEN = 9,
574 H_CC = 10,
575 H_X_FACE = 11
576};
577
578MsgInfo *procheader_parse_stream(FILE *fp, MsgFlags flags, gboolean full)
579{
580 static HeaderEntry hentry_full[] = {{"Date:", NULL, FALSE},
581 {"From:", NULL, TRUE},
582 {"To:", NULL, TRUE},
583 {"Newsgroups:", NULL, TRUE},
584 {"Subject:", NULL, TRUE},
585 {"Message-Id:", NULL, FALSE},
586 {"References:", NULL, FALSE},
587 {"In-Reply-To:", NULL, FALSE},
588 {"Content-Type:", NULL, FALSE},
589 {"Seen:", NULL, FALSE},
590 {"Cc:", NULL, TRUE},
591 {"X-Face:", NULL, FALSE},
592 {NULL, NULL, FALSE}};
593
594 static HeaderEntry hentry_short[] = {{"Date:", NULL, FALSE},
595 {"From:", NULL, TRUE},
596 {"To:", NULL, TRUE},
597 {"Newsgroups:", NULL, TRUE},
598 {"Subject:", NULL, TRUE},
599 {"Message-Id:", NULL, FALSE},
600 {"References:", NULL, FALSE},
601 {"In-Reply-To:", NULL, FALSE},
602 {"Content-Type:", NULL, FALSE},
603 {"Seen:", NULL, FALSE},
604 {NULL, NULL, FALSE}};
605
606 MsgInfo *msginfo;
607 gchar buf[BUFFSIZE];
608 gchar *p;
609 gchar *hp;
610 HeaderEntry *hentry;
611 gint hnum;
612 gchar *from = NULL, *to = NULL, *subject = NULL, *cc = NULL;
613 gchar *charset = NULL;
614
615 hentry = full ? hentry_full : hentry_short;
616
617 if (MSG_IS_QUEUED(flags)) {
618 while (fgets(buf, sizeof(buf), fp) != NULL)
619 if (buf[0] == '\r' || buf[0] == '\n') break;
620 }
621
622 msginfo = g_new0(MsgInfo, 1);
623 msginfo->flags = flags;
624 msginfo->references = NULL;
625 msginfo->inreplyto = NULL;
626
627 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
628 != -1) {
629 hp = buf + strlen(hentry[hnum].name);
630 while (*hp == ' ' || *hp == '\t') hp++;
631
632 switch (hnum) {
633 case H_DATE:
634 if (msginfo->date) break;
635 msginfo->date_t =
636 procheader_date_parse(NULL, hp, 0);
637 msginfo->date = g_strdup(hp);
638 break;
639 case H_FROM:
640 if (from) break;
641 from = g_strdup(hp);
642 break;
643 case H_TO:
644 if (to) {
645 p = to;
646 to = g_strconcat(p, ", ", hp, NULL);
647 g_free(p);
648 } else
649 to = g_strdup(hp);
650 break;
651 case H_NEWSGROUPS:
652 if (msginfo->newsgroups) {
653 p = msginfo->newsgroups;
654 msginfo->newsgroups =
655 g_strconcat(p, ",", hp, NULL);
656 g_free(p);
657 } else
658 msginfo->newsgroups = g_strdup(buf + 12);
659 break;
660 case H_SUBJECT:
661 if (msginfo->subject) break;
662 subject = g_strdup(hp);
663 break;
664 case H_MSG_ID:
665 if (msginfo->msgid) break;
666
667 extract_parenthesis(hp, '<', '>');
668 remove_space(hp);
669 msginfo->msgid = g_strdup(hp);
670 break;
671 case H_REFERENCES:
672 msginfo->references =
673 references_list_prepend(msginfo->references,
674 hp);
675 break;
676 case H_IN_REPLY_TO:
677 if (msginfo->inreplyto) break;
678
679 eliminate_parenthesis(hp, '(', ')');
680 if ((p = strrchr(hp, '<')) != NULL &&
681 strchr(p + 1, '>') != NULL) {
682 extract_parenthesis(p, '<', '>');
683 remove_space(p);
684 if (*p != '\0')
685 msginfo->inreplyto = g_strdup(p);
686 }
687 break;
688 case H_CONTENT_TYPE:
689 if (!g_ascii_strncasecmp(hp, "multipart", 9)) {
690 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_MIME);
691 } else {
692 if (!g_ascii_strncasecmp(hp, "text/html", 9)) {
693 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_MIME_HTML);
694 }
695 if (!charset) {
696 procmime_scan_content_type_str
697 (hp, NULL, &charset, NULL, NULL);
698 }
699 }
700 break;
701 case H_SEEN:
702 /* mnews Seen header */
703 MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_NEW|MSG_UNREAD);
704 break;
705 case H_CC:
706 if (cc) {
707 p = cc;
708 cc = g_strconcat(p, ", ", hp, NULL);
709 g_free(p);
710 } else
711 cc = g_strdup(hp);
712 break;
713 case H_X_FACE:
714 if (msginfo->xface) break;
715 msginfo->xface = g_strdup(hp);
716 break;
717 default:
718 break;
719 }
720 }
721
722 if (from) {
723 msginfo->from = conv_unmime_header(from, charset);
724 subst_control(msginfo->from, ' ');
725 msginfo->fromname = procheader_get_fromname(msginfo->from);
726 g_free(from);
727 }
728 if (to) {
729 msginfo->to = conv_unmime_header(to, charset);
730 subst_control(msginfo->to, ' ');
731 g_free(to);
732 }
733 if (subject) {
734 msginfo->subject = conv_unmime_header(subject, charset);
735 subst_control(msginfo->subject, ' ');
736 g_free(subject);
737 }
738 if (cc) {
739 msginfo->cc = conv_unmime_header(cc, charset);
740 subst_control(msginfo->cc, ' ');
741 g_free(cc);
742 }
743
744 if (!msginfo->inreplyto && msginfo->references)
745 msginfo->inreplyto =
746 g_strdup((gchar *)msginfo->references->data);
747
748 if (MSG_IS_MIME(msginfo->flags)) {
749 MimeInfo *mimeinfo, *part;
750 gboolean has_html = FALSE;
751
752 part = mimeinfo = procmime_scan_message_stream(fp);
753 while (part) {
754 if (part->mime_type != MIME_TEXT &&
755 part->mime_type != MIME_TEXT_HTML &&
756 part->mime_type != MIME_MULTIPART)
757 break;
758 if (part->mime_type == MIME_TEXT_HTML)
759 has_html = TRUE;
760 part = procmime_mimeinfo_next(part);
761 }
762
763 if (has_html && !part) {
764 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_MIME_HTML);
765 }
766
767 procmime_mimeinfo_free_all(mimeinfo);
768 }
769
770 g_free(charset);
771
772 return msginfo;
773}
774
775gchar *procheader_get_fromname(const gchar *str)
776{
777 gchar *tmp, *name;
778
779 tmp = g_strdup(str);
780
781 if (*tmp == '\"') {
782 extract_quote_with_escape(tmp, '\"');
783 g_strstrip(tmp);
784 } else if (strchr(tmp, '<')) {
785 eliminate_parenthesis(tmp, '<', '>');
786 g_strstrip(tmp);
787 if (*tmp == '\0') {
788 strcpy(tmp, str);
789 extract_parenthesis(tmp, '<', '>');
790 g_strstrip(tmp);
791 }
792 } else if (strchr(tmp, '(')) {
793 extract_parenthesis_with_escape(tmp, '(', ')');
794 g_strstrip(tmp);
795 }
796
797 if (*tmp == '\0') {
798 g_free(tmp);
799 name = g_strdup(str);
800 } else
801 name = tmp;
802
803 return name;
804}
805
806gchar *procheader_get_toname(const gchar *str)
807{
808 GSList *addr_list, *cur;
809 GString *toname;
810 gchar *name;
811
812 if (strchr(str, ',') == NULL)
813 return procheader_get_fromname(str);
814
815 addr_list = address_list_append_orig(NULL, str);
816 toname = g_string_new(NULL);
817
818 for (cur = addr_list; cur != NULL; cur = cur->next) {
819 name = procheader_get_fromname((gchar *)cur->data);
820 g_string_append(toname, name);
821 g_free(name);
822 if (cur->next)
823 g_string_append(toname, ", ");
824 }
825
826 slist_free_strings(addr_list);
827
828 return g_string_free(toname, FALSE);
829}
830
831static gint procheader_scan_date_string(const gchar *str,
832 gchar *weekday, gint *day,
833 gchar *month, gint *year,
834 gint *hh, gint *mm, gint *ss,
835 gchar *zone)
836{
837 gint result;
838
839 *zone = '\0';
840 result = sscanf(str, "%10s %d %9s %d %2d:%2d:%2d %5s",
841 weekday, day, month, year, hh, mm, ss, zone);
842 if (result >= 7) return 0;
843
844 result = sscanf(str, "%3s,%d %9s %d %2d:%2d:%2d %5s",
845 weekday, day, month, year, hh, mm, ss, zone);
846 if (result >= 7) return 0;
847
848 result = sscanf(str, "%3s,%d %9s %d %2d.%2d.%2d %5s",
849 weekday, day, month, year, hh, mm, ss, zone);
850 if (result >= 7) return 0;
851
852 result = sscanf(str, "%3s %d, %9s %d %2d:%2d:%2d %5s",
853 weekday, day, month, year, hh, mm, ss, zone);
854 if (result >= 7) return 0;
855
856 result = sscanf(str, "%d %9s %d %2d:%2d:%2d %5s",
857 day, month, year, hh, mm, ss, zone);
858 if (result >= 6) return 0;
859
860 result = sscanf(str, "%d-%2s-%2d %2d:%2d:%2d",
861 year, month, day, hh, mm, ss);
862 if (result == 6) return 0;
863
864 *ss = 0;
865 result = sscanf(str, "%10s %d %9s %d %2d:%2d %5s",
866 weekday, day, month, year, hh, mm, zone);
867 if (result >= 6) return 0;
868
869 result = sscanf(str, "%d %9s %d %2d:%2d %5s",
870 day, month, year, hh, mm, zone);
871 if (result >= 5) return 0;
872
873 return -1;
874}
875
876stime_t procheader_date_parse(gchar *dest, const gchar *src, gint len)
877{
878 static gchar monthstr[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
879 gchar weekday[11];
880 gint day;
881 gchar month[10];
882 gint year;
883 gint hh, mm, ss;
884 gchar zone[6];
885 GDateMonth dmonth = G_DATE_BAD_MONTH;
886 struct tm t;
887 gchar *p;
888 time_t timer_;
889 time_t tz_offset;
890 stime_t timer;
891
892 if (procheader_scan_date_string(src, weekday, &day, month, &year,
893 &hh, &mm, &ss, zone) < 0) {
894 g_warning("procheader_scan_date_string: date parse failed: %s", src);
895 if (dest && len > 0)
896 strncpy2(dest, src, len);
897 return 0;
898 }
899
900 /* Y2K compliant :) */
901 if (year < 1000) {
902 if (year < 50)
903 year += 2000;
904 else
905 year += 1900;
906 }
907
908 month[3] = '\0';
909 if (g_ascii_isdigit(month[0])) {
910 dmonth = atoi(month);
911 } else {
912 for (p = monthstr; *p != '\0'; p += 3) {
913 if (!g_ascii_strncasecmp(p, month, 3)) {
914 dmonth = (gint)(p - monthstr) / 3 + 1;
915 break;
916 }
917 }
918 }
919
920 t.tm_sec = ss;
921 t.tm_min = mm;
922 t.tm_hour = hh;
923 t.tm_mday = day;
924 t.tm_mon = dmonth - 1;
925 t.tm_year = year - 1900;
926 t.tm_wday = 0;
927 t.tm_yday = 0;
928 t.tm_isdst = -1;
929
930 timer_ = mktime(&t);
931 if (timer_ == -1) {
932 if (year >= 2038) {
933 g_warning("mktime: date overflow: %s", src);
934 timer_ = G_MAXINT - 12 * 3600;
935 } else {
936 g_warning("mktime: can't convert date: %s", src);
937 if (dest)
938 dest[0] = '\0';
939 return 0;
940 }
941 }
942
943 timer = timer_;
944 if (timer < G_MAXINT - 12 * 3600) {
945 tz_offset = remote_tzoffset_sec(zone);
946 if (tz_offset != -1)
947 timer += tzoffset_sec(&timer) - tz_offset;
948 }
949
950 if (dest)
951 procheader_date_get_localtime(dest, len, timer);
952
953 return timer;
954}
955
956void procheader_date_get_localtime(gchar *dest, gint len, const stime_t timer)
957{
958 time_t timer_ = timer;
959 struct tm *lt;
960 gchar *default_format = "%y/%m/%d(%a) %H:%M";
961 gchar *buf;
962 gchar tmp[BUFFSIZE];
963
964 lt = localtime(&timer_);
965 if (!lt) {
966 g_warning("can't get localtime of %ld\n", timer);
967 dest[0] = '\0';
968 return;
969 }
970
971 if (prefs_common.date_format)
972 strftime(tmp, sizeof(tmp), prefs_common.date_format, lt);
973 else
974 strftime(tmp, sizeof(tmp), default_format, lt);
975
976 buf = conv_localetodisp(tmp, NULL);
977 strncpy2(dest, buf, len);
978 g_free(buf);
979}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __PROCHEADER_H__
21#define __PROCHEADER_H__
22
23#include <glib.h>
24#include <stdio.h>
25#include <time.h>
26
27#include "procmsg.h"
28
29typedef struct _HeaderEntry HeaderEntry;
30typedef struct _Header Header;
31
32struct _HeaderEntry
33{
34 gchar *name;
35 gchar *body;
36 gboolean unfold;
37};
38
39struct _Header
40{
41 gchar *name;
42 gchar *body;
43};
44
45gint procheader_get_one_field (gchar *buf,
46 size_t len,
47 FILE *fp,
48 HeaderEntry hentry[]);
49gchar *procheader_get_unfolded_line (gchar *buf,
50 size_t len,
51 FILE *fp);
52
53GSList *procheader_get_header_list_from_file (const gchar *file);
54GSList *procheader_get_header_list (FILE *fp);
55GSList *procheader_get_header_list_from_msginfo (MsgInfo *msginfo);
56GSList *procheader_add_header_list (GSList *hlist,
57 const gchar *header_name,
58 const gchar *body);
59GSList *procheader_copy_header_list (GSList *hlist);
60GSList *procheader_merge_header_list (GSList *hlist1,
61 GSList *hlist2);
62GSList *procheader_merge_header_list_dup (GSList *hlist1,
63 GSList *hlist2);
64gint procheader_find_header_list (GSList *hlist,
65 const gchar *header_name);
66void procheader_header_list_destroy (GSList *hlist);
67
68GPtrArray *procheader_get_header_array (FILE *fp,
69 const gchar *encoding);
70GPtrArray *procheader_get_header_array_asis (FILE *fp,
71 const gchar *encoding);
72GPtrArray *procheader_get_header_array_for_display
73 (FILE *fp,
74 const gchar *encoding);
75void procheader_header_array_destroy (GPtrArray *harray);
76
77void procheader_header_free (Header *header);
78
79void procheader_get_header_fields (FILE *fp,
80 HeaderEntry hentry[]);
81MsgInfo *procheader_parse_file (const gchar *file,
82 MsgFlags flags,
83 gboolean full);
84MsgInfo *procheader_parse_str (const gchar *str,
85 MsgFlags flags,
86 gboolean full);
87MsgInfo *procheader_parse_stream (FILE *fp,
88 MsgFlags flags,
89 gboolean full);
90
91gchar *procheader_get_fromname (const gchar *str);
92gchar *procheader_get_toname (const gchar *str);
93
94stime_t procheader_date_parse (gchar *dest,
95 const gchar *src,
96 gint len);
97void procheader_date_get_localtime (gchar *dest,
98 gint len,
99 const stime_t timer);
100
101#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2017 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <stdio.h>
29#include <string.h>
30#include <locale.h>
31#include <ctype.h>
32
33#include "procmime.h"
34#include "procheader.h"
35#include "base64.h"
36#include "quoted-printable.h"
37#include "uuencode.h"
38#include "html.h"
39#include "codeconv.h"
40#include "utils.h"
41#include "prefs_common.h"
42
43#undef MIME_DEBUG
44/* #define MIME_DEBUG */
45#ifdef MIME_DEBUG
46# define mime_debug_print debug_print
47#else
48# define mime_debug_print 1 ? (void)0 : debug_print
49#endif
50
51#define MAX_MIME_LEVEL 64
52
53static GHashTable *procmime_get_mime_type_table (void);
54static GList *procmime_get_mime_type_list (const gchar *file);
55
56
57MimeInfo *procmime_mimeinfo_new(void)
58{
59 MimeInfo *mimeinfo;
60
61 mimeinfo = g_new0(MimeInfo, 1);
62 mimeinfo->mime_type = MIME_UNKNOWN;
63 mimeinfo->encoding_type = ENC_UNKNOWN;
64
65 return mimeinfo;
66}
67
68void procmime_mimeinfo_free_all(MimeInfo *mimeinfo)
69{
70 while (mimeinfo != NULL) {
71 MimeInfo *next;
72
73 g_free(mimeinfo->encoding);
74 g_free(mimeinfo->content_type);
75 g_free(mimeinfo->charset);
76 g_free(mimeinfo->name);
77 g_free(mimeinfo->boundary);
78 g_free(mimeinfo->content_disposition);
79 g_free(mimeinfo->filename);
80
81 g_free(mimeinfo->sigstatus);
82 g_free(mimeinfo->sigstatus_full);
83
84 procmime_mimeinfo_free_all(mimeinfo->sub);
85 procmime_mimeinfo_free_all(mimeinfo->children);
86 procmime_mimeinfo_free_all(mimeinfo->plaintext);
87
88 next = mimeinfo->next;
89 g_free(mimeinfo);
90 mimeinfo = next;
91 }
92}
93
94MimeInfo *procmime_mimeinfo_insert(MimeInfo *parent, MimeInfo *mimeinfo)
95{
96 MimeInfo *child = parent->children;
97
98 if (!child)
99 parent->children = mimeinfo;
100 else {
101 while (child->next != NULL)
102 child = child->next;
103
104 child->next = mimeinfo;
105 }
106
107 mimeinfo->parent = parent;
108 mimeinfo->level = parent->level + 1;
109
110 return mimeinfo;
111}
112
113#if 0
114void procmime_mimeinfo_replace(MimeInfo *old, MimeInfo *new)
115{
116 MimeInfo *parent = old->parent;
117 MimeInfo *child;
118
119 g_return_if_fail(parent != NULL);
120 g_return_if_fail(new->next == NULL);
121
122 for (child = parent->children; child && child != old;
123 child = child->next)
124 ;
125 if (!child) {
126 g_warning("oops: parent can't find it's own child");
127 return;
128 }
129 procmime_mimeinfo_free_all(old);
130
131 if (child == parent->children) {
132 new->next = parent->children->next;
133 parent->children = new;
134 } else {
135 new->next = child->next;
136 child = new;
137 }
138}
139#endif
140
141MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo)
142{
143 if (!mimeinfo) return NULL;
144
145 if (mimeinfo->children)
146 return mimeinfo->children;
147 if (mimeinfo->sub)
148 return mimeinfo->sub;
149 if (mimeinfo->next)
150 return mimeinfo->next;
151
152 if (mimeinfo->main) {
153 mimeinfo = mimeinfo->main;
154 if (mimeinfo->next)
155 return mimeinfo->next;
156 }
157
158 for (mimeinfo = mimeinfo->parent; mimeinfo != NULL;
159 mimeinfo = mimeinfo->parent) {
160 if (mimeinfo->next)
161 return mimeinfo->next;
162 if (mimeinfo->main) {
163 mimeinfo = mimeinfo->main;
164 if (mimeinfo->next)
165 return mimeinfo->next;
166 }
167 }
168
169 return NULL;
170}
171
172#if 0
173void procmime_dump_mimeinfo(MimeInfo *mimeinfo)
174{
175 gint i;
176
177 g_print("\n");
178
179 for (; mimeinfo != NULL; mimeinfo = procmime_mimeinfo_next(mimeinfo)) {
180 for (i = 0; i < mimeinfo->level; i++)
181 g_print(" ");
182 g_print("%s%s\n", mimeinfo->main ? "sub: " : "",
183 mimeinfo->content_type);
184 }
185}
186#endif
187
188MimeInfo *procmime_scan_message(MsgInfo *msginfo)
189{
190 FILE *fp;
191 MimeInfo *mimeinfo;
192
193 g_return_val_if_fail(msginfo != NULL, NULL);
194
195 if ((fp = procmsg_open_message_decrypted(msginfo, &mimeinfo)) == NULL)
196 return NULL;
197
198 if (mimeinfo) {
199 mimeinfo->size = msginfo->size;
200 mimeinfo->content_size = get_left_file_size(fp);
201 if (mimeinfo->encoding_type == ENC_BASE64)
202 mimeinfo->content_size = mimeinfo->content_size / 4 * 3;
203 if (mimeinfo->mime_type == MIME_MULTIPART ||
204 mimeinfo->mime_type == MIME_MESSAGE_RFC822)
205 procmime_scan_multipart_message(mimeinfo, fp);
206 }
207
208 fclose(fp);
209
210 return mimeinfo;
211}
212
213MimeInfo *procmime_scan_message_stream(FILE *fp)
214{
215 MimeInfo *mimeinfo;
216 glong fpos;
217
218 g_return_val_if_fail(fp != NULL, NULL);
219
220 if (fseek(fp, 0L, SEEK_SET) < 0) {
221 FILE_OP_ERROR("procmime_scan_message_stream()", "fseek");
222 return NULL;
223 }
224
225 mimeinfo = procmime_scan_mime_header(fp);
226
227 if (mimeinfo) {
228 fpos = ftell(fp);
229 mimeinfo->content_size = get_left_file_size(fp);
230 mimeinfo->size = fpos + mimeinfo->content_size;
231 if (mimeinfo->encoding_type == ENC_BASE64)
232 mimeinfo->content_size = mimeinfo->content_size / 4 * 3;
233 if (mimeinfo->mime_type == MIME_MULTIPART ||
234 mimeinfo->mime_type == MIME_MESSAGE_RFC822)
235 procmime_scan_multipart_message(mimeinfo, fp);
236 }
237
238 return mimeinfo;
239}
240
241void procmime_scan_multipart_message(MimeInfo *mimeinfo, FILE *fp)
242{
243 gchar *p;
244 gchar *boundary;
245 gint boundary_len = 0;
246 gchar *buf;
247 glong fpos, prev_fpos;
248
249 g_return_if_fail(mimeinfo != NULL);
250 g_return_if_fail(mimeinfo->mime_type == MIME_MULTIPART ||
251 mimeinfo->mime_type == MIME_MESSAGE_RFC822);
252
253 if (mimeinfo->mime_type == MIME_MULTIPART) {
254 g_return_if_fail(mimeinfo->boundary != NULL);
255 g_return_if_fail(mimeinfo->sub == NULL);
256 }
257 g_return_if_fail(fp != NULL);
258
259 buf = g_malloc(BUFFSIZE);
260
261 boundary = mimeinfo->boundary;
262
263 if (boundary) {
264 boundary_len = strlen(boundary);
265
266 /* look for first boundary */
267 while ((p = fgets(buf, BUFFSIZE, fp)) != NULL)
268 if (IS_BOUNDARY(buf, boundary, boundary_len)) break;
269 if (!p) {
270 g_free(buf);
271 return;
272 }
273 } else if (mimeinfo->parent && mimeinfo->parent->boundary) {
274 boundary = mimeinfo->parent->boundary;
275 boundary_len = strlen(boundary);
276 }
277
278 if ((fpos = ftell(fp)) < 0) {
279 perror("ftell");
280 g_free(buf);
281 return;
282 }
283
284 mime_debug_print("==== enter part\n");
285 mime_debug_print("level = %d\n", mimeinfo->level);
286
287 for (;;) {
288 MimeInfo *partinfo;
289 gboolean eom = FALSE;
290 glong content_pos;
291 gboolean is_base64;
292 gint len;
293 guint b64_content_len = 0;
294 gint b64_pad_len = 0;
295
296 prev_fpos = fpos;
297 mime_debug_print("prev_fpos: %ld\n", fpos);
298
299 /* scan part header */
300 if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
301 MimeInfo *sub;
302
303 mimeinfo->sub = sub = procmime_scan_mime_header(fp);
304 if (!sub) break;
305
306 mime_debug_print("message/rfc822 part (content-type: %s)\n",
307 sub->content_type);
308 sub->level = mimeinfo->level + 1;
309 sub->parent = mimeinfo->parent;
310 sub->main = mimeinfo;
311
312 partinfo = sub;
313 } else {
314 partinfo = procmime_scan_mime_header(fp);
315 if (!partinfo) break;
316 procmime_mimeinfo_insert(mimeinfo, partinfo);
317 mime_debug_print("content-type: %s\n",
318 partinfo->content_type);
319 if (partinfo->filename)
320 mime_debug_print("filename: %s\n", partinfo->filename);
321 else if (partinfo->name)
322 mime_debug_print("name: %s\n", partinfo->name);
323 }
324
325 /* begin content */
326 content_pos = ftell(fp);
327 mime_debug_print("content_pos: %ld\n", content_pos);
328
329 if (partinfo->mime_type == MIME_MULTIPART ||
330 partinfo->mime_type == MIME_MESSAGE_RFC822) {
331 if (partinfo->level < MAX_MIME_LEVEL) {
332 mime_debug_print("\n");
333 mime_debug_print("enter to child part:\n");
334 procmime_scan_multipart_message(partinfo, fp);
335 }
336 }
337
338 /* look for next boundary */
339 buf[0] = '\0';
340 is_base64 = partinfo->encoding_type == ENC_BASE64;
341 while ((p = fgets(buf, BUFFSIZE, fp)) != NULL) {
342 if (IS_BOUNDARY(buf, boundary, boundary_len)) {
343 if (buf[2 + boundary_len] == '-' &&
344 buf[2 + boundary_len + 1] == '-')
345 eom = TRUE;
346 break;
347 } else if (is_base64) {
348 const gchar *s;
349 for (s = buf; *s && *s != '\r' && *s != '\n';
350 ++s)
351 if (*s == '=')
352 ++b64_pad_len;
353 b64_content_len += s - buf;
354 }
355 }
356 if (p == NULL) {
357 /* broken MIME, or single part MIME message */
358 buf[0] = '\0';
359 eom = TRUE;
360 }
361 mime_debug_print("boundary: %s\n", buf);
362
363 fpos = ftell(fp);
364 mime_debug_print("fpos: %ld\n", fpos);
365
366 len = strlen(buf);
367 partinfo->size = fpos - prev_fpos - len;
368 if (is_base64)
369 partinfo->content_size =
370 b64_content_len / 4 * 3 - b64_pad_len;
371 else
372 partinfo->content_size = fpos - content_pos - len;
373 mime_debug_print("partinfo->size: %d\n", partinfo->size);
374 mime_debug_print("partinfo->content_size: %d\n",
375 partinfo->content_size);
376 if (partinfo->sub && !partinfo->sub->sub &&
377 !partinfo->sub->children) {
378 partinfo->sub->size =
379 fpos - partinfo->sub->fpos - strlen(buf);
380 mime_debug_print("partinfo->sub->size: %d\n",
381 partinfo->sub->size);
382 }
383
384 if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
385 if (len > 0 && fseek(fp, fpos - len, SEEK_SET) < 0)
386 perror("fseek");
387 break;
388 }
389
390 if (eom) break;
391 }
392
393 g_free(buf);
394 mime_debug_print("==== leave part\n");
395}
396
397void procmime_scan_encoding(MimeInfo *mimeinfo, const gchar *encoding)
398{
399 gchar *enc;
400
401 g_free(mimeinfo->encoding);
402 enc = mimeinfo->encoding = g_strstrip(g_strdup(encoding));
403
404 if (!g_ascii_strncasecmp(enc, "7bit", 4))
405 mimeinfo->encoding_type = ENC_7BIT;
406 else if (!g_ascii_strncasecmp(enc, "8bit", 4))
407 mimeinfo->encoding_type = ENC_8BIT;
408 else if (!g_ascii_strncasecmp(enc, "quoted-printable", 16))
409 mimeinfo->encoding_type = ENC_QUOTED_PRINTABLE;
410 else if (!g_ascii_strncasecmp(enc, "base64", 6))
411 mimeinfo->encoding_type = ENC_BASE64;
412 else if (!g_ascii_strncasecmp(enc, "x-uuencode", 10))
413 mimeinfo->encoding_type = ENC_X_UUENCODE;
414 else
415 mimeinfo->encoding_type = ENC_UNKNOWN;
416
417}
418
419void procmime_scan_content_type(MimeInfo *mimeinfo, const gchar *content_type)
420{
421 g_free(mimeinfo->content_type);
422 g_free(mimeinfo->charset);
423 g_free(mimeinfo->name);
424 g_free(mimeinfo->boundary);
425 mimeinfo->content_type = NULL;
426 mimeinfo->charset = NULL;
427 mimeinfo->name = NULL;
428 mimeinfo->boundary = NULL;
429
430 procmime_scan_content_type_str(content_type, &mimeinfo->content_type,
431 &mimeinfo->charset, &mimeinfo->name,
432 &mimeinfo->boundary);
433
434 mimeinfo->mime_type = procmime_scan_mime_type(mimeinfo->content_type);
435 if (mimeinfo->mime_type == MIME_MULTIPART && !mimeinfo->boundary)
436 mimeinfo->mime_type = MIME_TEXT;
437}
438
439typedef struct
440{
441 gchar *name;
442 gchar *value;
443} MimeParam;
444
445typedef struct
446{
447 gchar *hvalue;
448 GSList *plist;
449} MimeParams;
450
451static gchar *procmime_find_parameter_delimiter(const gchar *param,
452 const gchar **eq)
453{
454 register const gchar *p = param;
455 gboolean quoted = FALSE;
456 const gchar *delim = NULL;
457
458 while (*p) {
459 if (*p == '=')
460 break;
461 else if (*p == ';' || *p == '\r' || *p == '\n') {
462 delim = p;
463 break;
464 }
465 ++p;
466 }
467 if (*p != '=') {
468 *eq = NULL;
469 return (gchar *)delim;
470 }
471 *eq = p;
472
473 ++p;
474 while (g_ascii_isspace(*p))
475 ++p;
476 if (*p == '"') {
477 quoted = TRUE;
478 ++p;
479 }
480
481 while (*p) {
482 if (quoted == TRUE) {
483 if (*p == '"')
484 quoted = FALSE;
485 } else if (*p == ';' || *p == '\r' || *p == '\n') {
486 delim = p;
487 break;
488 }
489 ++p;
490 }
491
492 return (gchar *)delim;
493}
494
495static gchar *procmime_convert_value(const gchar *value, const gchar *charset)
496{
497 if (charset) {
498 gchar *utf8_value;
499
500 utf8_value = conv_codeset_strdup(value, charset, CS_INTERNAL);
501 if (utf8_value)
502 return utf8_value;
503 }
504
505 return g_strdup(value);
506}
507
508static MimeParams *procmime_parse_mime_parameter(const gchar *str)
509{
510 ConvADType ad_type;
511 gchar *tmp_param = NULL;
512 gchar *hvalue;
513 gchar *param, *name, *value;
514 gchar *charset = NULL, *lang = NULL;
515 const gchar *p, *delim;
516 gint count, prev_count;
517 gchar *cont_name;
518 gchar *cont_value;
519 MimeParam *mparam;
520 MimeParams *mparams;
521 GSList *plist = NULL;
522
523 if ((p = strchr(str, ';')))
524 hvalue = g_strndup(str, p - str);
525 else
526 hvalue = g_strdup(str);
527
528 g_strstrip(hvalue);
529
530 mparams = g_new(MimeParams, 1);
531 mparams->hvalue = hvalue;
532 mparams->plist = NULL;
533
534 if (!p)
535 return mparams;
536 ++p;
537
538 /* workaround for raw-JIS filename (Eudora etc.) */
539 ad_type = conv_get_autodetect_type();
540 if ((ad_type == C_AD_JAPANESE ||
541 (ad_type == C_AD_BY_LOCALE && conv_is_ja_locale())) &&
542 strstr(p, "\033$") != NULL) {
543 CodeConvFunc conv_func;
544 conv_func = conv_get_code_conv_func(NULL, NULL);
545 tmp_param = conv_func(p, NULL);
546 p = tmp_param;
547 debug_print("procmime_parse_mime_parameter(): raw-JIS header body detected: %s\n", str);
548 }
549
550 count = prev_count = -1;
551 cont_name = cont_value = NULL;
552
553 for (;;) {
554 gboolean encoded = FALSE;
555 gchar *begin;
556 gchar *dec_value;
557 const gchar *eq;
558 gchar *ast = NULL;
559
560 while (*p == ';' || g_ascii_isspace(*p))
561 ++p;
562 if (*p == '\0')
563 break;
564
565 delim = procmime_find_parameter_delimiter(p, &eq);
566 if (!eq)
567 break;
568 if (delim)
569 param = g_strndup(p, delim - p);
570 else
571 param = g_strdup(p);
572
573 name = g_strndup(p, eq - p);
574 g_strchomp(name);
575 if (*name != '*' && (ast = strchr(name, '*'))) {
576 const gchar *next = ast + 1;
577
578 if (*next == '\0') {
579 encoded = TRUE;
580 } else if (g_ascii_isdigit(*next)) {
581 count = atoi(next);
582 while (g_ascii_isdigit(*next))
583 ++next;
584 if (*next == '*')
585 encoded = TRUE;
586 if (prev_count + 1 != count) {
587 g_warning("procmime_parse_mime_parameter(): invalid count: %s\n", str);
588 g_free(name);
589 g_free(param);
590 break;
591 }
592 } else {
593 g_warning("procmime_parse_mime_parameter(): invalid name: %s\n", str);
594 g_free(name);
595 g_free(param);
596 break;
597 }
598
599 *ast = '\0';
600 }
601
602 value = g_strdup(param + (eq - p) + 1);
603 g_strstrip(value);
604 if (*value == '"')
605 extract_quote(value, '"');
606
607 begin = value;
608
609 if (encoded) {
610 gchar *sq1, *sq2;
611
612 if ((sq1 = strchr(value, '\''))) {
613 if (sq1 > value) {
614 if (charset)
615 g_free(charset);
616 charset = g_strndup(value, sq1 - value);
617 }
618 if ((sq2 = strchr(sq1 + 1, '\''))) {
619 if (sq2 > sq1 + 1) {
620 if (lang)
621 g_free(lang);
622 lang = g_strndup(sq1 + 1,
623 sq2 - sq1 - 1);
624 }
625 begin = sq2 + 1;
626 }
627 }
628 }
629
630#define CONCAT_CONT_VALUE(s) \
631{ \
632 if (cont_value) { \
633 gchar *tmp; \
634 tmp = g_strconcat(cont_value, s, NULL); \
635 g_free(cont_value); \
636 cont_value = tmp; \
637 } else \
638 cont_value = g_strdup(s); \
639}
640
641 if (count >= 0) {
642 if (count > 0 && cont_name) {
643 if (strcmp(cont_name, name) != 0) {
644 g_warning("procmime_parse_mime_parameter(): mismatch parameter name: %s\n", str);
645 g_free(name);
646 g_free(value);
647 g_free(param);
648 break;
649 }
650 } else
651 cont_name = g_strdup(name);
652
653 if (encoded) {
654 dec_value = g_malloc(strlen(begin) + 1);
655 decode_xdigit_encoded_str(dec_value, begin);
656 CONCAT_CONT_VALUE(dec_value);
657 g_free(dec_value);
658 } else {
659 CONCAT_CONT_VALUE(begin);
660 }
661 }
662
663#undef CONCAT_CONT_VALUE
664
665 if (count == -1 && cont_name && cont_value) {
666 mparam = g_new(MimeParam, 1);
667 mparam->name = cont_name;
668 cont_name = NULL;
669 mparam->value = procmime_convert_value
670 (cont_value, charset);
671 g_free(cont_value);
672 cont_value = NULL;
673 plist = g_slist_prepend(plist, mparam);
674 }
675
676 if (count == -1) {
677 mparam = g_new(MimeParam, 1);
678 mparam->name = name;
679 if (encoded) {
680 dec_value = g_malloc(strlen(begin) + 1);
681 decode_xdigit_encoded_str(dec_value, begin);
682 mparam->value = procmime_convert_value
683 (dec_value, charset);
684 g_free(dec_value);
685 } else {
686 if (!ast &&
687 (!g_ascii_strcasecmp(name, "name") ||
688 !g_ascii_strcasecmp(name, "filename")))
689 mparam->value =
690 conv_unmime_header(begin, NULL);
691 else
692 mparam->value = g_strdup(begin);
693 }
694 name = NULL;
695 plist = g_slist_prepend(plist, mparam);
696 }
697
698 g_free(name);
699 g_free(value);
700 g_free(param);
701
702 prev_count = count;
703 count = -1;
704
705 if (delim)
706 p = delim + 1;
707 else
708 break;
709 }
710
711 if (cont_name && cont_value) {
712 mparam = g_new(MimeParam, 1);
713 mparam->name = cont_name;
714 cont_name = NULL;
715 mparam->value = procmime_convert_value(cont_value, charset);
716 plist = g_slist_prepend(plist, mparam);
717 }
718
719 g_free(cont_name);
720 g_free(cont_value);
721 g_free(lang);
722 g_free(charset);
723 if (tmp_param)
724 g_free(tmp_param);
725
726 plist = g_slist_reverse(plist);
727 mparams->plist = plist;
728
729 return mparams;
730}
731
732static void procmime_mime_params_free(MimeParams *mparams)
733{
734 GSList *cur;
735
736 if (!mparams)
737 return;
738
739 g_free(mparams->hvalue);
740 for (cur = mparams->plist; cur != NULL; cur = cur->next) {
741 MimeParam *mparam = (MimeParam *)cur->data;
742 g_free(mparam->name);
743 g_free(mparam->value);
744 g_free(mparam);
745 }
746 g_slist_free(mparams->plist);
747 g_free(mparams);
748}
749
750void procmime_scan_content_type_str(const gchar *content_type,
751 gchar **mime_type, gchar **charset,
752 gchar **name, gchar **boundary)
753{
754 MimeParams *mparams;
755 GSList *cur;
756
757 mparams = procmime_parse_mime_parameter(content_type);
758
759 if (mime_type)
760 *mime_type = g_strdup(mparams->hvalue);
761
762 for (cur = mparams->plist; cur != NULL; cur = cur->next) {
763 MimeParam *param = (MimeParam *)cur->data;
764 if (charset && !g_ascii_strcasecmp(param->name, "charset")) {
765 *charset = g_strdup(param->value);
766 eliminate_parenthesis(*charset, '(', ')');
767 g_strstrip(*charset);
768 charset = NULL;
769 } else if (name && !g_ascii_strcasecmp(param->name, "name")) {
770 *name = g_strdup(param->value);
771 name = NULL;
772 } else if (boundary &&
773 !g_ascii_strcasecmp(param->name, "boundary")) {
774 *boundary = g_strdup(param->value);
775 boundary = NULL;
776 }
777 }
778
779 procmime_mime_params_free(mparams);
780}
781
782void procmime_scan_content_type_partial(const gchar *content_type,
783 gint *total, gchar **part_id,
784 gint *number)
785{
786 MimeParams *mparams;
787 GSList *cur;
788 gchar *id_str = NULL;
789 gint t = 0, n = 0;
790
791 *total = 0;
792 *part_id = NULL;
793 *number = 0;
794
795 mparams = procmime_parse_mime_parameter(content_type);
796
797 if (!mparams->hvalue ||
798 g_ascii_strcasecmp(mparams->hvalue, "message/partial") != 0) {
799 procmime_mime_params_free(mparams);
800 return;
801 }
802
803 for (cur = mparams->plist; cur != NULL; cur = cur->next) {
804 MimeParam *param = (MimeParam *)cur->data;
805 if (!g_ascii_strcasecmp(param->name, "total")) {
806 t = atoi(param->value);
807 } else if (!id_str && !g_ascii_strcasecmp(param->name, "id")) {
808 id_str = g_strdup(param->value);
809 } else if (!g_ascii_strcasecmp(param->name, "number")) {
810 n = atoi(param->value);
811 }
812 }
813
814 procmime_mime_params_free(mparams);
815
816 if (n > 0 && (t == 0 || t >= n) && id_str) {
817 *total = t;
818 *part_id = id_str;
819 *number = n;
820 } else {
821 g_free(id_str);
822 }
823}
824
825void procmime_scan_content_disposition(MimeInfo *mimeinfo,
826 const gchar *content_disposition)
827{
828 MimeParams *mparams;
829 GSList *cur;
830
831 mparams = procmime_parse_mime_parameter(content_disposition);
832
833 mimeinfo->content_disposition = g_strdup(mparams->hvalue);
834
835 for (cur = mparams->plist; cur != NULL; cur = cur->next) {
836 MimeParam *param = (MimeParam *)cur->data;
837 if (!g_ascii_strcasecmp(param->name, "filename")) {
838 mimeinfo->filename = g_strdup(param->value);
839 break;
840 }
841 }
842
843 procmime_mime_params_free(mparams);
844}
845
846enum
847{
848 H_CONTENT_TRANSFER_ENCODING = 0,
849 H_CONTENT_TYPE = 1,
850 H_CONTENT_DISPOSITION = 2
851};
852
853MimeInfo *procmime_scan_mime_header(FILE *fp)
854{
855 static HeaderEntry hentry[] = {{"Content-Transfer-Encoding:",
856 NULL, FALSE},
857 {"Content-Type:", NULL, TRUE},
858 {"Content-Disposition:",
859 NULL, TRUE},
860 {NULL, NULL, FALSE}};
861 gchar buf[BUFFSIZE];
862 gint hnum;
863 HeaderEntry *hp;
864 MimeInfo *mimeinfo;
865
866 g_return_val_if_fail(fp != NULL, NULL);
867
868 mimeinfo = procmime_mimeinfo_new();
869 mimeinfo->mime_type = MIME_TEXT;
870 mimeinfo->encoding_type = ENC_7BIT;
871 mimeinfo->fpos = ftell(fp);
872
873 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
874 != -1) {
875 hp = hentry + hnum;
876
877 if (H_CONTENT_TRANSFER_ENCODING == hnum) {
878 procmime_scan_encoding
879 (mimeinfo, buf + strlen(hp->name));
880 } else if (H_CONTENT_TYPE == hnum) {
881 procmime_scan_content_type
882 (mimeinfo, buf + strlen(hp->name));
883 } else if (H_CONTENT_DISPOSITION == hnum) {
884 procmime_scan_content_disposition
885 (mimeinfo, buf + strlen(hp->name));
886 }
887 }
888
889 if (mimeinfo->mime_type == MIME_APPLICATION_OCTET_STREAM &&
890 (mimeinfo->filename || mimeinfo->name)) {
891 const gchar *type;
892 type = procmime_get_mime_type
893 (mimeinfo->filename ? mimeinfo->filename
894 : mimeinfo->name);
895 if (type)
896 mimeinfo->mime_type = procmime_scan_mime_type(type);
897 }
898
899 if (!mimeinfo->content_type)
900 mimeinfo->content_type = g_strdup("text/plain");
901
902 return mimeinfo;
903}
904
905static gint procmime_normalize_lbreak(FILE *infp, FILE *outfp)
906{
907 gchar buf[BUFFSIZE];
908 gint len;
909
910 g_return_val_if_fail(infp != NULL, -1);
911 g_return_val_if_fail(outfp != NULL, -1);
912
913 while (fgets(buf, sizeof(buf), infp) != NULL) {
914 len = strlen(buf);
915 if (len == sizeof(buf) - 1 && buf[len - 1] != '\n') {
916 if (buf[len - 1] == '\r') {
917 ungetc('\r', infp);
918 buf[len - 1] = '\0';
919 }
920 fputs(buf, outfp);
921 continue;
922 }
923#ifdef G_OS_WIN32
924 strretchomp(buf);
925 fputs(buf, outfp);
926 fputs("\r\n", outfp);
927#else
928 strcrchomp(buf);
929 fputs(buf, outfp);
930#endif
931 }
932
933 return 0;
934}
935
936FILE *procmime_decode_content(FILE *outfp, FILE *infp, MimeInfo *mimeinfo)
937{
938 gchar buf[BUFFSIZE];
939 gchar *boundary = NULL;
940 gint boundary_len = 0;
941 gboolean tmp_file = FALSE;
942 gboolean normalize_lbreak = FALSE;
943 ContentType content_type;
944
945 g_return_val_if_fail(infp != NULL, NULL);
946 g_return_val_if_fail(mimeinfo != NULL, NULL);
947
948 if (!outfp) {
949 outfp = my_tmpfile();
950 if (!outfp) {
951 perror("tmpfile");
952 return NULL;
953 }
954 tmp_file = TRUE;
955 }
956
957 if (mimeinfo->parent && mimeinfo->parent->boundary) {
958 boundary = mimeinfo->parent->boundary;
959 boundary_len = strlen(boundary);
960 }
961
962 content_type = procmime_scan_mime_type(mimeinfo->content_type);
963 if (content_type == MIME_TEXT ||
964 content_type == MIME_TEXT_HTML) {
965 normalize_lbreak = TRUE;
966 }
967
968 if (mimeinfo->encoding_type == ENC_QUOTED_PRINTABLE) {
969 FILE *tmpfp = outfp;
970 gchar prev_empty_line[3] = "";
971
972 if (normalize_lbreak) {
973 tmpfp = my_tmpfile();
974 if (!tmpfp) {
975 perror("tmpfile");
976 if (tmp_file) fclose(outfp);
977 return NULL;
978 }
979 }
980
981 while (fgets(buf, sizeof(buf), infp) != NULL &&
982 (!boundary ||
983 !IS_BOUNDARY(buf, boundary, boundary_len))) {
984 gint len;
985
986 if (prev_empty_line[0]) {
987 fputs(prev_empty_line, tmpfp);
988 prev_empty_line[0] = '\0';
989 }
990
991 if (buf[0] == '\n' ||
992 (buf[0] == '\r' && buf[1] == '\n'))
993 strcpy(prev_empty_line, buf);
994 else {
995 len = qp_decode_line(buf);
996 fwrite(buf, len, 1, tmpfp);
997 }
998 }
999 if (!boundary && prev_empty_line[0])
1000 fputs(prev_empty_line, tmpfp);
1001
1002 if (normalize_lbreak) {
1003 if (fflush(tmpfp) == EOF) {
1004 perror("fflush");
1005 fclose(tmpfp);
1006 if (tmp_file) fclose(outfp);
1007 return NULL;
1008 }
1009 rewind(tmpfp);
1010 procmime_normalize_lbreak(tmpfp, outfp);
1011 fclose(tmpfp);
1012 }
1013 } else if (mimeinfo->encoding_type == ENC_BASE64) {
1014 gchar outbuf[BUFFSIZE];
1015 gint len;
1016 Base64Decoder *decoder;
1017 FILE *tmpfp = outfp;
1018
1019 if (normalize_lbreak) {
1020 tmpfp = my_tmpfile();
1021 if (!tmpfp) {
1022 perror("tmpfile");
1023 if (tmp_file) fclose(outfp);
1024 return NULL;
1025 }
1026 }
1027
1028 decoder = base64_decoder_new();
1029 while (fgets(buf, sizeof(buf), infp) != NULL &&
1030 (!boundary ||
1031 !IS_BOUNDARY(buf, boundary, boundary_len))) {
1032 len = base64_decoder_decode(decoder, buf,
1033 (guchar *)outbuf);
1034 if (len < 0) {
1035 g_warning("Bad BASE64 content\n");
1036 break;
1037 }
1038 fwrite(outbuf, sizeof(gchar), len, tmpfp);
1039 }
1040 base64_decoder_free(decoder);
1041
1042 if (normalize_lbreak) {
1043 if (fflush(tmpfp) == EOF) {
1044 perror("fflush");
1045 fclose(tmpfp);
1046 if (tmp_file) fclose(outfp);
1047 return NULL;
1048 }
1049 rewind(tmpfp);
1050 procmime_normalize_lbreak(tmpfp, outfp);
1051 fclose(tmpfp);
1052 }
1053 } else if (mimeinfo->encoding_type == ENC_X_UUENCODE) {
1054 gchar outbuf[BUFFSIZE];
1055 gint len;
1056 gboolean flag = FALSE;
1057
1058 while (fgets(buf, sizeof(buf), infp) != NULL &&
1059 (!boundary ||
1060 !IS_BOUNDARY(buf, boundary, boundary_len))) {
1061 if(!flag && strncmp(buf,"begin ", 6)) continue;
1062
1063 if (flag) {
1064 len = fromuutobits(outbuf, buf);
1065 if (len <= 0) {
1066 if (len < 0)
1067 g_warning("Bad UUENCODE content(%d)\n", len);
1068 break;
1069 }
1070 fwrite(outbuf, sizeof(gchar), len, outfp);
1071 } else
1072 flag = TRUE;
1073 }
1074 } else {
1075 gchar prev_empty_line[3] = "";
1076 gint len;
1077 gboolean cont_line = FALSE;
1078
1079 while (fgets(buf, sizeof(buf), infp) != NULL &&
1080 (!boundary ||
1081 !IS_BOUNDARY(buf, boundary, boundary_len))) {
1082 if (prev_empty_line[0]) {
1083 fputs(prev_empty_line, outfp);
1084 prev_empty_line[0] = '\0';
1085 }
1086
1087 len = strlen(buf);
1088 if (len == sizeof(buf) - 1 &&
1089 buf[len - 1] != '\n') {
1090 if (buf[len - 1] == '\r') {
1091 ungetc('\r', infp);
1092 buf[len - 1] = '\0';
1093 }
1094 fputs(buf, outfp);
1095 cont_line = TRUE;
1096 continue;
1097 }
1098
1099 if (normalize_lbreak) {
1100#ifdef G_OS_WIN32
1101 strretchomp(buf);
1102 if (!cont_line && buf[0] == '\0')
1103 strcpy(prev_empty_line, "\r\n");
1104 else {
1105 fputs(buf, outfp);
1106 fputs("\r\n", outfp);
1107 }
1108#else
1109 strcrchomp(buf);
1110 if (!cont_line && buf[0] == '\n')
1111 strcpy(prev_empty_line, "\n");
1112 else
1113 fputs(buf, outfp);
1114#endif
1115 } else {
1116 if (!cont_line &&
1117 (buf[0] == '\n' ||
1118 (buf[0] == '\r' && buf[1] == '\n')))
1119 strcpy(prev_empty_line, buf);
1120 else
1121 fputs(buf, outfp);
1122 }
1123
1124 cont_line = FALSE;
1125 }
1126 if (!boundary && prev_empty_line[0])
1127 fputs(prev_empty_line, outfp);
1128 }
1129
1130 if (fflush(outfp) == EOF)
1131 perror("fflush");
1132 if (ferror(outfp) != 0) {
1133 g_warning("procmime_decode_content(): Can't write to temporary file\n");
1134 if (tmp_file) fclose(outfp);
1135 return NULL;
1136 }
1137
1138 if (tmp_file) rewind(outfp);
1139 return outfp;
1140}
1141
1142gint procmime_get_part(const gchar *outfile, const gchar *infile,
1143 MimeInfo *mimeinfo)
1144{
1145 FILE *infp;
1146 gint ret;
1147
1148 g_return_val_if_fail(outfile != NULL, -1);
1149 g_return_val_if_fail(infile != NULL, -1);
1150 g_return_val_if_fail(mimeinfo != NULL, -1);
1151
1152 if ((infp = g_fopen(infile, "rb")) == NULL) {
1153 FILE_OP_ERROR(infile, "fopen");
1154 return -1;
1155 }
1156 ret = procmime_get_part_fp(outfile, infp, mimeinfo);
1157 fclose(infp);
1158
1159 return ret;
1160}
1161
1162gint procmime_get_part_fp(const gchar *outfile, FILE *infp, MimeInfo *mimeinfo)
1163{
1164 FILE *outfp;
1165 gchar buf[BUFFSIZE];
1166
1167 g_return_val_if_fail(outfile != NULL, -1);
1168 g_return_val_if_fail(infp != NULL, -1);
1169 g_return_val_if_fail(mimeinfo != NULL, -1);
1170
1171 if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) {
1172 FILE_OP_ERROR("procmime_get_part_fp()", "fseek");
1173 return -1;
1174 }
1175 if ((outfp = g_fopen(outfile, "wb")) == NULL) {
1176 FILE_OP_ERROR(outfile, "fopen");
1177 return -1;
1178 }
1179
1180 while (fgets(buf, sizeof(buf), infp) != NULL)
1181 if (buf[0] == '\r' || buf[0] == '\n') break;
1182
1183 if (procmime_decode_content(outfp, infp, mimeinfo) == NULL) {
1184 fclose(outfp);
1185 g_unlink(outfile);
1186 return -1;
1187 }
1188
1189 if (fclose(outfp) == EOF) {
1190 FILE_OP_ERROR(outfile, "fclose");
1191 g_unlink(outfile);
1192 return -1;
1193 }
1194
1195 return 0;
1196}
1197
1198FILE *procmime_get_part_fp_fp(FILE *outfp, FILE *infp, MimeInfo *mimeinfo)
1199{
1200 gchar buf[BUFFSIZE];
1201
1202 g_return_val_if_fail(infp != NULL, NULL);
1203 g_return_val_if_fail(mimeinfo != NULL, NULL);
1204
1205 if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) {
1206 FILE_OP_ERROR("procmime_get_part_fp()", "fseek");
1207 return NULL;
1208 }
1209
1210 while (fgets(buf, sizeof(buf), infp) != NULL)
1211 if (buf[0] == '\r' || buf[0] == '\n') break;
1212
1213 if ((outfp = procmime_decode_content(outfp, infp, mimeinfo)) == NULL) {
1214 return NULL;
1215 }
1216
1217 return outfp;
1218}
1219
1220gint procmime_get_all_parts(const gchar *dir, const gchar *infile,
1221 MimeInfo *mimeinfo)
1222{
1223 FILE *fp;
1224 MimeInfo *partinfo;
1225 gchar *base, *filename;
1226
1227 g_return_val_if_fail(dir != NULL, -1);
1228 g_return_val_if_fail(infile != NULL, -1);
1229 g_return_val_if_fail(mimeinfo != NULL, -1);
1230
1231 if (!is_dir_exist(dir)) {
1232 g_warning("%s: directory not exist.\n", dir);
1233 return -1;
1234 }
1235
1236 if ((fp = g_fopen(infile, "rb")) == NULL) {
1237 FILE_OP_ERROR(infile, "fopen");
1238 return -1;
1239 }
1240
1241 for (partinfo = mimeinfo; partinfo != NULL;
1242 partinfo = procmime_mimeinfo_next(partinfo)) {
1243 if (partinfo->filename || partinfo->name) {
1244 gint count = 1;
1245
1246 base = procmime_get_part_file_name(partinfo);
1247 filename = g_strconcat(dir, G_DIR_SEPARATOR_S, base,
1248 NULL);
1249
1250 while (is_file_entry_exist(filename)) {
1251 gchar *base_alt;
1252
1253 base_alt = get_alt_filename(base, count++);
1254 g_free(filename);
1255 filename = g_strconcat
1256 (dir, G_DIR_SEPARATOR_S, base_alt,
1257 NULL);
1258 g_free(base_alt);
1259 }
1260
1261 procmime_get_part_fp(filename, fp, partinfo);
1262
1263 g_free(filename);
1264 g_free(base);
1265 }
1266 }
1267
1268 fclose(fp);
1269
1270 return 0;
1271}
1272
1273FILE *procmime_get_text_content(MimeInfo *mimeinfo, FILE *infp,
1274 const gchar *encoding)
1275{
1276 FILE *tmpfp, *outfp;
1277 const gchar *src_encoding;
1278 gboolean conv_fail = FALSE;
1279 gchar buf[BUFFSIZE];
1280
1281 g_return_val_if_fail(mimeinfo != NULL, NULL);
1282 g_return_val_if_fail(infp != NULL, NULL);
1283 g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT ||
1284 mimeinfo->mime_type == MIME_TEXT_HTML, NULL);
1285
1286 if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) {
1287 perror("fseek");
1288 return NULL;
1289 }
1290
1291 while (fgets(buf, sizeof(buf), infp) != NULL)
1292 if (buf[0] == '\r' || buf[0] == '\n') break;
1293
1294 tmpfp = procmime_decode_content(NULL, infp, mimeinfo);
1295 if (!tmpfp)
1296 return NULL;
1297
1298 if ((outfp = my_tmpfile()) == NULL) {
1299 perror("tmpfile");
1300 fclose(tmpfp);
1301 return NULL;
1302 }
1303
1304 src_encoding = prefs_common.force_charset ? prefs_common.force_charset
1305 : mimeinfo->charset ? mimeinfo->charset
1306 : prefs_common.default_encoding;
1307
1308 if (mimeinfo->mime_type == MIME_TEXT) {
1309 while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
1310 gchar *str;
1311
1312 str = conv_codeset_strdup(buf, src_encoding, encoding);
1313 if (str) {
1314 fputs(str, outfp);
1315 g_free(str);
1316 } else {
1317 conv_fail = TRUE;
1318 fputs(buf, outfp);
1319 }
1320 }
1321 } else if (mimeinfo->mime_type == MIME_TEXT_HTML) {
1322 HTMLParser *parser;
1323 CodeConverter *conv;
1324 const gchar *str;
1325
1326 conv = conv_code_converter_new(src_encoding, encoding);
1327 parser = html_parser_new(tmpfp, conv);
1328 while ((str = html_parse(parser)) != NULL) {
1329 fputs(str, outfp);
1330 }
1331 html_parser_destroy(parser);
1332 conv_code_converter_destroy(conv);
1333 }
1334
1335 if (conv_fail)
1336 g_warning(_("procmime_get_text_content(): Code conversion failed.\n"));
1337
1338 fclose(tmpfp);
1339 if (fflush(outfp) == EOF) {
1340 perror("fflush");
1341 fclose(outfp);
1342 return NULL;
1343 }
1344 rewind(outfp);
1345
1346 return outfp;
1347}
1348
1349/* search the first text part of (multipart) MIME message,
1350 decode, convert it and output to outfp. */
1351FILE *procmime_get_first_text_content(MsgInfo *msginfo, const gchar *encoding)
1352{
1353 FILE *infp, *outfp = NULL;
1354 MimeInfo *mimeinfo, *partinfo;
1355
1356 g_return_val_if_fail(msginfo != NULL, NULL);
1357
1358 mimeinfo = procmime_scan_message(msginfo);
1359 if (!mimeinfo) return NULL;
1360
1361 if ((infp = procmsg_open_message(msginfo)) == NULL) {
1362 procmime_mimeinfo_free_all(mimeinfo);
1363 return NULL;
1364 }
1365
1366 partinfo = mimeinfo;
1367 while (partinfo && partinfo->mime_type != MIME_TEXT)
1368 partinfo = procmime_mimeinfo_next(partinfo);
1369 if (!partinfo) {
1370 partinfo = mimeinfo;
1371 while (partinfo && partinfo->mime_type != MIME_TEXT_HTML)
1372 partinfo = procmime_mimeinfo_next(partinfo);
1373 }
1374
1375 if (partinfo)
1376 outfp = procmime_get_text_content(partinfo, infp, encoding);
1377
1378 fclose(infp);
1379 procmime_mimeinfo_free_all(mimeinfo);
1380
1381 return outfp;
1382}
1383
1384gboolean procmime_find_string_part(MimeInfo *mimeinfo, const gchar *filename,
1385 const gchar *str, StrFindFunc find_func)
1386{
1387
1388 FILE *infp, *outfp;
1389 gchar buf[BUFFSIZE];
1390
1391 g_return_val_if_fail(mimeinfo != NULL, FALSE);
1392 g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT ||
1393 mimeinfo->mime_type == MIME_TEXT_HTML, FALSE);
1394 g_return_val_if_fail(str != NULL, FALSE);
1395 g_return_val_if_fail(find_func != NULL, FALSE);
1396
1397 if ((infp = g_fopen(filename, "rb")) == NULL) {
1398 FILE_OP_ERROR(filename, "fopen");
1399 return FALSE;
1400 }
1401
1402 outfp = procmime_get_text_content(mimeinfo, infp, NULL);
1403 fclose(infp);
1404
1405 if (!outfp)
1406 return FALSE;
1407
1408 while (fgets(buf, sizeof(buf), outfp) != NULL) {
1409 strretchomp(buf);
1410 if (find_func(buf, str)) {
1411 fclose(outfp);
1412 return TRUE;
1413 }
1414 }
1415
1416 fclose(outfp);
1417
1418 return FALSE;
1419}
1420
1421gboolean procmime_find_string(MsgInfo *msginfo, const gchar *str,
1422 StrFindFunc find_func)
1423{
1424 MimeInfo *mimeinfo;
1425 MimeInfo *partinfo;
1426 gchar *filename;
1427 gboolean found = FALSE;
1428
1429 g_return_val_if_fail(msginfo != NULL, FALSE);
1430 g_return_val_if_fail(str != NULL, FALSE);
1431 g_return_val_if_fail(find_func != NULL, FALSE);
1432
1433 filename = procmsg_get_message_file(msginfo);
1434 if (!filename) return FALSE;
1435 mimeinfo = procmime_scan_message(msginfo);
1436
1437 for (partinfo = mimeinfo; partinfo != NULL;
1438 partinfo = procmime_mimeinfo_next(partinfo)) {
1439 if (partinfo->mime_type == MIME_TEXT ||
1440 partinfo->mime_type == MIME_TEXT_HTML) {
1441 if (procmime_find_string_part
1442 (partinfo, filename, str, find_func) == TRUE) {
1443 found = TRUE;
1444 break;
1445 }
1446 }
1447 }
1448
1449 procmime_mimeinfo_free_all(mimeinfo);
1450 g_free(filename);
1451
1452 return found;
1453}
1454
1455gchar *procmime_get_part_file_name(MimeInfo *mimeinfo)
1456{
1457 gchar *base;
1458 const gchar *base_;
1459
1460 base_ = mimeinfo->filename ? mimeinfo->filename
1461 : mimeinfo->name ? mimeinfo->name : "mimetmp";
1462 base_ = g_basename(base_);
1463 if (*base_ == '\0') base_ = "mimetmp";
1464 base = conv_filename_from_utf8(base_);
1465 subst_for_filename(base);
1466
1467 return base;
1468}
1469
1470gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo)
1471{
1472 static guint32 id = 0;
1473 gchar *base;
1474 gchar *filename = NULL;
1475 gchar f_prefix[10];
1476
1477 g_return_val_if_fail(mimeinfo != NULL, NULL);
1478
1479 if (MIME_TEXT_HTML == mimeinfo->mime_type)
1480 base = g_strdup("mimetmp.html");
1481 else
1482 base = procmime_get_part_file_name(mimeinfo);
1483
1484 do {
1485 g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++);
1486 if (filename)
1487 g_free(filename);
1488 filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
1489 f_prefix, base, NULL);
1490 } while (is_file_entry_exist(filename));
1491
1492 g_free(base);
1493
1494 debug_print("procmime_get_tmp_file_name: %s\n", filename);
1495
1496 return filename;
1497}
1498
1499gchar *procmime_get_tmp_file_name_for_user(MimeInfo *mimeinfo)
1500{
1501 gchar *base;
1502 gchar *filename;
1503 gint count = 1;
1504
1505 g_return_val_if_fail(mimeinfo != NULL, NULL);
1506
1507 if (MIME_TEXT_HTML == mimeinfo->mime_type)
1508 base = g_strdup("mimetmp.html");
1509 else
1510 base = procmime_get_part_file_name(mimeinfo);
1511
1512 filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
1513 base, NULL);
1514 while (is_file_entry_exist(filename)) {
1515 gchar *base_alt;
1516
1517 base_alt = get_alt_filename(base, count++);
1518 g_free(filename);
1519 filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
1520 base_alt, NULL);
1521 g_free(base_alt);
1522 }
1523
1524 g_free(base);
1525
1526 debug_print("procmime_get_tmp_file_name_for_user: %s\n", filename);
1527
1528 return filename;
1529}
1530
1531ContentType procmime_scan_mime_type(const gchar *mime_type)
1532{
1533 ContentType type;
1534
1535 if (!g_ascii_strncasecmp(mime_type, "text/html", 9))
1536 type = MIME_TEXT_HTML;
1537 else if (!g_ascii_strncasecmp(mime_type, "text/", 5))
1538 type = MIME_TEXT;
1539 else if (!g_ascii_strncasecmp(mime_type, "message/rfc822", 14))
1540 type = MIME_MESSAGE_RFC822;
1541 else if (!g_ascii_strncasecmp(mime_type, "message/", 8))
1542 type = MIME_TEXT;
1543 else if (!g_ascii_strncasecmp(mime_type, "application/octet-stream",
1544 24))
1545 type = MIME_APPLICATION_OCTET_STREAM;
1546 else if (!g_ascii_strncasecmp(mime_type, "application/", 12))
1547 type = MIME_APPLICATION;
1548 else if (!g_ascii_strncasecmp(mime_type, "multipart/", 10))
1549 type = MIME_MULTIPART;
1550 else if (!g_ascii_strncasecmp(mime_type, "image/", 6))
1551 type = MIME_IMAGE;
1552 else if (!g_ascii_strncasecmp(mime_type, "audio/", 6))
1553 type = MIME_AUDIO;
1554 else if (!g_ascii_strncasecmp(mime_type, "video/", 6))
1555 type = MIME_VIDEO;
1556 else if (!g_ascii_strcasecmp(mime_type, "text"))
1557 type = MIME_TEXT;
1558 else
1559 type = MIME_UNKNOWN;
1560
1561 return type;
1562}
1563
1564static GList *mime_type_list = NULL;
1565
1566gchar *procmime_get_mime_type(const gchar *filename)
1567{
1568 static GHashTable *mime_type_table = NULL;
1569 MimeType *mime_type;
1570 const gchar *p;
1571 gchar ext[64];
1572 static gboolean no_mime_type_table = FALSE;
1573
1574 if (no_mime_type_table)
1575 return NULL;
1576
1577 if (!mime_type_table) {
1578 mime_type_table = procmime_get_mime_type_table();
1579 if (!mime_type_table) {
1580 no_mime_type_table = TRUE;
1581 return NULL;
1582 }
1583 }
1584
1585 filename = g_basename(filename);
1586 p = strrchr(filename, '.');
1587 if (!p) return NULL;
1588
1589 strncpy2(ext, p + 1, sizeof(ext));
1590 g_strdown(ext);
1591 mime_type = g_hash_table_lookup(mime_type_table, ext);
1592 if (mime_type) {
1593 gchar *str;
1594
1595 str = g_strconcat(mime_type->type, "/", mime_type->sub_type,
1596 NULL);
1597 return str;
1598 }
1599
1600 return NULL;
1601}
1602
1603static GHashTable *procmime_get_mime_type_table(void)
1604{
1605 GHashTable *table = NULL;
1606 GList *cur;
1607 MimeType *mime_type;
1608 gchar **exts;
1609
1610 if (!mime_type_list) {
1611 GList *list;
1612 gchar *dir;
1613
1614#ifdef G_OS_WIN32
1615 dir = g_strconcat(get_startup_dir(),
1616 G_DIR_SEPARATOR_S "etc" G_DIR_SEPARATOR_S
1617 "mime.types", NULL);
1618 mime_type_list = procmime_get_mime_type_list(dir);
1619 g_free(dir);
1620#else
1621 mime_type_list =
1622 procmime_get_mime_type_list(SYSCONFDIR "/mime.types");
1623 if (!mime_type_list)
1624 mime_type_list =
1625 procmime_get_mime_type_list("/etc/mime.types");
1626#endif
1627 dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1628 "mime.types", NULL);
1629 list = procmime_get_mime_type_list(dir);
1630 g_free(dir);
1631 mime_type_list = g_list_concat(mime_type_list, list);
1632
1633 if (!mime_type_list) {
1634 debug_print("mime.types not found\n");
1635 return NULL;
1636 }
1637 }
1638
1639 table = g_hash_table_new(g_str_hash, g_str_equal);
1640
1641 for (cur = mime_type_list; cur != NULL; cur = cur->next) {
1642 gint i;
1643 gchar *key;
1644
1645 mime_type = (MimeType *)cur->data;
1646
1647 if (!mime_type->extension) continue;
1648
1649 exts = g_strsplit(mime_type->extension, " ", 16);
1650 for (i = 0; exts[i] != NULL; i++) {
1651 /* make the key case insensitive */
1652 g_strdown(exts[i]);
1653 /* use previously dup'd key on overwriting */
1654 if (g_hash_table_lookup(table, exts[i]))
1655 key = exts[i];
1656 else
1657 key = g_strdup(exts[i]);
1658 g_hash_table_insert(table, key, mime_type);
1659 }
1660 g_strfreev(exts);
1661 }
1662
1663 return table;
1664}
1665
1666static GList *procmime_get_mime_type_list(const gchar *file)
1667{
1668 GList *list = NULL;
1669 FILE *fp;
1670 gchar buf[BUFFSIZE];
1671 gchar *p;
1672 gchar *delim;
1673 MimeType *mime_type;
1674
1675 if ((fp = g_fopen(file, "rb")) == NULL) return NULL;
1676
1677 debug_print("Reading %s ...\n", file);
1678
1679 while (fgets(buf, sizeof(buf), fp) != NULL) {
1680 p = strchr(buf, '#');
1681 if (p) *p = '\0';
1682 g_strstrip(buf);
1683
1684 p = buf;
1685 while (*p && !g_ascii_isspace(*p)) p++;
1686 if (*p) {
1687 *p = '\0';
1688 p++;
1689 }
1690 delim = strchr(buf, '/');
1691 if (delim == NULL) continue;
1692 *delim = '\0';
1693
1694 mime_type = g_new(MimeType, 1);
1695 mime_type->type = g_strdup(buf);
1696 mime_type->sub_type = g_strdup(delim + 1);
1697
1698 while (*p && g_ascii_isspace(*p)) p++;
1699 if (*p)
1700 mime_type->extension = g_strdup(p);
1701 else
1702 mime_type->extension = NULL;
1703
1704 list = g_list_append(list, mime_type);
1705 }
1706
1707 fclose(fp);
1708
1709 if (!list)
1710 g_warning("Can't read mime.types\n");
1711
1712 return list;
1713}
1714
1715static GList *mailcap_list = NULL;
1716
1717static GList *procmime_parse_mailcap(const gchar *file)
1718{
1719 GList *list = NULL;
1720 FILE *fp;
1721 gchar buf[BUFFSIZE];
1722 MailCap *mailcap;
1723
1724 if ((fp = g_fopen(file, "rb")) == NULL) return NULL;
1725
1726 while (fgets(buf, sizeof(buf), fp) != NULL) {
1727 gint i;
1728 gchar *p;
1729 gchar **strv;
1730
1731 p = strchr(buf, '#');
1732 if (p) *p = '\0';
1733 g_strstrip(buf);
1734
1735 strv = strsplit_with_quote(buf, ";", 0);
1736 if (!strv)
1737 continue;
1738
1739 for (i = 0; strv[i] != NULL; ++i)
1740 g_strstrip(strv[i]);
1741
1742 if (!strv[0] || *strv[0] == '\0' ||
1743 !strv[1] || *strv[1] == '\0') {
1744 g_strfreev(strv);
1745 continue;
1746 }
1747
1748 mailcap = g_new(MailCap, 1);
1749 mailcap->mime_type = g_strdup(strv[0]);
1750 mailcap->cmdline_fmt = g_strdup(strv[1]);
1751 mailcap->needs_terminal = FALSE;
1752
1753 for (i = 0; strv[i] != NULL; ++i) {
1754 if (strcmp(strv[i], "needsterminal") == 0)
1755 mailcap->needs_terminal = TRUE;
1756 }
1757
1758 g_strfreev(strv);
1759
1760 list = g_list_append(list, mailcap);
1761 }
1762
1763 fclose(fp);
1764
1765 return list;
1766}
1767
1768gint procmime_execute_open_file(const gchar *file, const gchar *mime_type)
1769{
1770 gchar *mime_type_ = NULL;
1771 GList *cur;
1772 MailCap *mailcap;
1773 gchar *cmdline;
1774 gint ret = -1;
1775 static gboolean mailcap_list_init = FALSE;
1776
1777 g_return_val_if_fail(file != NULL, -1);
1778
1779 if (!mime_type ||
1780 g_ascii_strcasecmp(mime_type, "application/octet-stream") == 0) {
1781 gchar *tmp;
1782 tmp = procmime_get_mime_type(file);
1783 if (!tmp)
1784 return -1;
1785 mime_type_ = g_ascii_strdown(tmp, -1);
1786 g_free(tmp);
1787 } else
1788 mime_type_ = g_ascii_strdown(mime_type, -1);
1789
1790 if (!mailcap_list_init && !mailcap_list) {
1791 GList *list;
1792 gchar *path;
1793
1794 path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "mailcap",
1795 NULL);
1796 mailcap_list = procmime_parse_mailcap(path);
1797 g_free(path);
1798#ifdef G_OS_WIN32
1799 path = g_strconcat(get_startup_dir(), G_DIR_SEPARATOR_S "etc"
1800 G_DIR_SEPARATOR_S "mailcap", NULL);
1801 list = procmime_parse_mailcap(path);
1802 g_free(path);
1803#else
1804 if (!mailcap_list) {
1805 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1806 ".mailcap", NULL);
1807 mailcap_list = procmime_parse_mailcap(path);
1808 g_free(path);
1809 }
1810 list = procmime_parse_mailcap(SYSCONFDIR "/mailcap");
1811 if (!list)
1812 list = procmime_parse_mailcap("/etc/mailcap");
1813#endif
1814 mailcap_list = g_list_concat(mailcap_list, list);
1815
1816 mailcap_list_init = TRUE;
1817 }
1818
1819 for (cur = mailcap_list; cur != NULL; cur = cur->next) {
1820 mailcap = (MailCap *)cur->data;
1821
1822 if (!g_pattern_match_simple(mailcap->mime_type, mime_type_))
1823 continue;
1824 if (mailcap->needs_terminal)
1825 continue;
1826
1827 if (str_find_format_times(mailcap->cmdline_fmt, 's') == 1)
1828 cmdline = g_strdup_printf(mailcap->cmdline_fmt, file);
1829 else
1830 cmdline = g_strconcat(mailcap->cmdline_fmt, " \"", file,
1831 "\"", NULL);
1832 ret = execute_command_line(cmdline, TRUE);
1833 g_free(cmdline);
1834 break;
1835 }
1836
1837 g_free(mime_type_);
1838
1839 return ret;
1840}
1841
1842EncodingType procmime_get_encoding_for_charset(const gchar *charset)
1843{
1844 if (!charset)
1845 return ENC_8BIT;
1846 else if (!g_ascii_strncasecmp(charset, "ISO-2022-", 9) ||
1847 !g_ascii_strcasecmp(charset, "US-ASCII"))
1848 return ENC_7BIT;
1849 else if (!g_ascii_strcasecmp(charset, "ISO-8859-5") ||
1850 !g_ascii_strncasecmp(charset, "KOI8-", 5) ||
1851 !g_ascii_strcasecmp(charset, "Windows-1251"))
1852 return ENC_8BIT;
1853 else if (!g_ascii_strncasecmp(charset, "ISO-8859-", 9))
1854 return ENC_QUOTED_PRINTABLE;
1855 else
1856 return ENC_8BIT;
1857}
1858
1859EncodingType procmime_get_encoding_for_text_file(const gchar *file)
1860{
1861 FILE *fp;
1862 guchar buf[BUFFSIZE];
1863 size_t len;
1864 size_t octet_chars = 0;
1865 size_t total_len = 0;
1866 gfloat octet_percentage;
1867
1868 if ((fp = g_fopen(file, "rb")) == NULL) {
1869 FILE_OP_ERROR(file, "fopen");
1870 return ENC_UNKNOWN;
1871 }
1872
1873 while ((len = fread(buf, sizeof(guchar), sizeof(buf), fp)) > 0) {
1874 guchar *p;
1875 gint i;
1876
1877 for (p = buf, i = 0; i < len; ++p, ++i) {
1878 if ((*p & 0x80) || *p == 0x00 || *p == 0x7f) {
1879 ++octet_chars;
1880 }
1881 }
1882 total_len += len;
1883 }
1884
1885 fclose(fp);
1886
1887 if (total_len > 0)
1888 octet_percentage = (gfloat)octet_chars / (gfloat)total_len;
1889 else
1890 octet_percentage = 0.0;
1891
1892 debug_print("procmime_get_encoding_for_text_file(): "
1893 "8bit chars: %d / %d (%f%%)\n", octet_chars, total_len,
1894 100.0 * octet_percentage);
1895
1896 if (octet_percentage > 0.20) {
1897 debug_print("using BASE64\n");
1898 return ENC_BASE64;
1899 } else if (octet_chars > 0) {
1900 debug_print("using quoted-printable\n");
1901 return ENC_QUOTED_PRINTABLE;
1902 } else {
1903 debug_print("using 7bit\n");
1904 return ENC_7BIT;
1905 }
1906}
1907
1908EncodingType procmime_get_encoding_for_str(const gchar *str)
1909{
1910 const guchar *p;
1911 size_t octet_chars = 0;
1912 size_t total_len = 0;
1913 gfloat octet_percentage;
1914
1915 total_len = strlen(str);
1916
1917 for (p = (const guchar *)str; *p != '\0'; ++p) {
1918 if ((*p & 0x80) || *p == 0x7f) {
1919 ++octet_chars;
1920 }
1921 }
1922
1923 if (total_len > 0)
1924 octet_percentage = (gfloat)octet_chars / (gfloat)total_len;
1925 else
1926 octet_percentage = 0.0;
1927
1928 debug_print("procmime_get_encoding_for_str(): "
1929 "8bit chars: %d / %d (%f%%)\n", octet_chars, total_len,
1930 100.0 * octet_percentage);
1931
1932 if (octet_percentage > 0.20) {
1933 debug_print("using BASE64\n");
1934 return ENC_BASE64;
1935 } else if (octet_chars > 0) {
1936 debug_print("using quoted-printable\n");
1937 return ENC_QUOTED_PRINTABLE;
1938 } else {
1939 debug_print("using 7bit\n");
1940 return ENC_7BIT;
1941 }
1942}
1943
1944const gchar *procmime_get_encoding_str(EncodingType encoding)
1945{
1946 static const gchar *encoding_str[] = {
1947 "7bit", "8bit", "quoted-printable", "base64", "x-uuencode",
1948 NULL
1949 };
1950
1951 if (encoding >= ENC_7BIT && encoding <= ENC_UNKNOWN)
1952 return encoding_str[encoding];
1953 else
1954 return NULL;
1955}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2010 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __PROCMIME_H__
21#define __PROCMIME_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28#include <stdio.h>
29
30typedef struct _MimeType MimeType;
31typedef struct _MailCap MailCap;
32typedef struct _MimeInfo MimeInfo;
33
34#include "procmsg.h"
35#include "utils.h"
36
37typedef enum
38{
39 ENC_7BIT,
40 ENC_8BIT,
41 ENC_QUOTED_PRINTABLE,
42 ENC_BASE64,
43 ENC_X_UUENCODE,
44 ENC_UNKNOWN
45} EncodingType;
46
47typedef enum
48{
49 MIME_TEXT,
50 MIME_TEXT_HTML,
51 MIME_MESSAGE_RFC822,
52 MIME_APPLICATION,
53 MIME_APPLICATION_OCTET_STREAM,
54 MIME_MULTIPART,
55 MIME_IMAGE,
56 MIME_AUDIO,
57 MIME_VIDEO,
58 MIME_UNKNOWN
59} ContentType;
60
61struct _MimeType
62{
63 gchar *type;
64 gchar *sub_type;
65
66 gchar *extension;
67};
68
69struct _MailCap
70{
71 gchar *mime_type;
72 gchar *cmdline_fmt;
73 gboolean needs_terminal;
74};
75
76/*
77 * An example of MimeInfo structure:
78 *
79 * multipart/mixed root <-+ parent
80 * |
81 * multipart/alternative children <-+ parent
82 * |
83 * text/plain children --+
84 * |
85 * text/html next <-+
86 *
87 * message/rfc822 next <-+ main
88 * |
89 * sub (capsulated message)
90 *
91 * image/jpeg next
92 */
93
94struct _MimeInfo
95{
96 gchar *encoding;
97
98 EncodingType encoding_type;
99 ContentType mime_type;
100
101 gchar *content_type;
102 gchar *charset;
103 gchar *name;
104 gchar *boundary;
105
106 gchar *content_disposition;
107 gchar *filename;
108
109 glong fpos;
110 guint size;
111 guint content_size;
112
113 MimeInfo *main;
114 MimeInfo *sub;
115
116 MimeInfo *next;
117 MimeInfo *parent;
118 MimeInfo *children;
119
120 MimeInfo *plaintext;
121 gchar *sigstatus;
122 gchar *sigstatus_full;
123
124 gint level;
125};
126
127#define IS_BOUNDARY(s, bnd, len) \
128 (bnd && s[0] == '-' && s[1] == '-' && !strncmp(s + 2, bnd, len))
129
130/* MimeInfo handling */
131
132MimeInfo *procmime_mimeinfo_new (void);
133void procmime_mimeinfo_free_all (MimeInfo *mimeinfo);
134
135MimeInfo *procmime_mimeinfo_insert (MimeInfo *parent,
136 MimeInfo *mimeinfo);
137#if 0
138void procmime_mimeinfo_replace (MimeInfo *old,
139 MimeInfo *new);
140#endif
141
142MimeInfo *procmime_mimeinfo_next (MimeInfo *mimeinfo);
143
144MimeInfo *procmime_scan_message (MsgInfo *msginfo);
145MimeInfo *procmime_scan_message_stream (FILE *fp);
146void procmime_scan_multipart_message (MimeInfo *mimeinfo,
147 FILE *fp);
148
149/* scan headers */
150
151void procmime_scan_encoding (MimeInfo *mimeinfo,
152 const gchar *encoding);
153void procmime_scan_content_type (MimeInfo *mimeinfo,
154 const gchar *content_type);
155void procmime_scan_content_type_str (const gchar *content_type,
156 gchar **mime_type,
157 gchar **charset,
158 gchar **name,
159 gchar **boundary);
160void procmime_scan_content_type_partial (const gchar *content_type,
161 gint *total,
162 gchar **part_id,
163 gint *number);
164void procmime_scan_content_disposition (MimeInfo *mimeinfo,
165 const gchar *content_disposition);
166MimeInfo *procmime_scan_mime_header (FILE *fp);
167
168FILE *procmime_decode_content (FILE *outfp,
169 FILE *infp,
170 MimeInfo *mimeinfo);
171gint procmime_get_part (const gchar *outfile,
172 const gchar *infile,
173 MimeInfo *mimeinfo);
174gint procmime_get_part_fp (const gchar *outfile,
175 FILE *infp,
176 MimeInfo *mimeinfo);
177FILE *procmime_get_part_fp_fp (FILE *outfp,
178 FILE *infp,
179 MimeInfo *mimeinfo);
180gint procmime_get_all_parts (const gchar *dir,
181 const gchar *infile,
182 MimeInfo *mimeinfo);
183FILE *procmime_get_text_content (MimeInfo *mimeinfo,
184 FILE *infp,
185 const gchar *encoding);
186FILE *procmime_get_first_text_content (MsgInfo *msginfo,
187 const gchar *encoding);
188
189gboolean procmime_find_string_part (MimeInfo *mimeinfo,
190 const gchar *filename,
191 const gchar *str,
192 StrFindFunc find_func);
193gboolean procmime_find_string (MsgInfo *msginfo,
194 const gchar *str,
195 StrFindFunc find_func);
196
197gchar *procmime_get_part_file_name (MimeInfo *mimeinfo);
198gchar *procmime_get_tmp_file_name (MimeInfo *mimeinfo);
199gchar *procmime_get_tmp_file_name_for_user
200 (MimeInfo *mimeinfo);
201
202ContentType procmime_scan_mime_type (const gchar *mime_type);
203gchar *procmime_get_mime_type (const gchar *filename);
204
205gint procmime_execute_open_file (const gchar *file,
206 const gchar *mime_type);
207
208EncodingType procmime_get_encoding_for_charset (const gchar *charset);
209EncodingType procmime_get_encoding_for_text_file(const gchar *file);
210EncodingType procmime_get_encoding_for_str (const gchar *str);
211const gchar *procmime_get_encoding_str (EncodingType encoding);
212
213#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include "defs.h"
21
22#include <glib.h>
23#include <glib/gi18n.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <errno.h>
27
28#include "utils.h"
29#include "procmsg.h"
30#include "procheader.h"
31#include "account.h"
32#include "procmime.h"
33#include "prefs_common.h"
34#include "folder.h"
35#include "codeconv.h"
36
37typedef struct _MsgFlagInfo {
38 guint msgnum;
39 MsgFlags flags;
40} MsgFlagInfo;
41
42static GSList *procmsg_read_cache_queue (FolderItem *item,
43 gboolean scan_file);
44
45static void mark_sum_func (gpointer key,
46 gpointer value,
47 gpointer data);
48
49static GHashTable *procmsg_read_mark_file (FolderItem *item);
50static void procmsg_write_mark_file (FolderItem *item,
51 GHashTable *mark_table);
52
53static GMappedFile *procmsg_open_cache_file_mmap(FolderItem *item,
54 DataOpenMode mode);
55
56static gint procmsg_cmp_by_mark (gconstpointer a,
57 gconstpointer b);
58static gint procmsg_cmp_by_unread (gconstpointer a,
59 gconstpointer b);
60static gint procmsg_cmp_by_mime (gconstpointer a,
61 gconstpointer b);
62static gint procmsg_cmp_by_label (gconstpointer a,
63 gconstpointer b);
64static gint procmsg_cmp_by_number (gconstpointer a,
65 gconstpointer b);
66static gint procmsg_cmp_by_size (gconstpointer a,
67 gconstpointer b);
68static gint procmsg_cmp_by_date (gconstpointer a,
69 gconstpointer b);
70static gint procmsg_cmp_by_from (gconstpointer a,
71 gconstpointer b);
72static gint procmsg_cmp_by_to (gconstpointer a,
73 gconstpointer b);
74static gint procmsg_cmp_by_subject (gconstpointer a,
75 gconstpointer b);
76
77
78GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
79{
80 GHashTable *msg_table;
81
82 if (mlist == NULL) return NULL;
83
84 msg_table = g_hash_table_new(NULL, g_direct_equal);
85 procmsg_msg_hash_table_append(msg_table, mlist);
86
87 return msg_table;
88}
89
90void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
91{
92 GSList *cur;
93 MsgInfo *msginfo;
94
95 if (msg_table == NULL || mlist == NULL) return;
96
97 for (cur = mlist; cur != NULL; cur = cur->next) {
98 msginfo = (MsgInfo *)cur->data;
99
100 g_hash_table_insert(msg_table,
101 GUINT_TO_POINTER(msginfo->msgnum),
102 msginfo);
103 }
104}
105
106GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
107{
108 GHashTable *msg_table;
109 GSList *cur;
110 MsgInfo *msginfo;
111
112 if (mlist == NULL) return NULL;
113
114 msg_table = g_hash_table_new(NULL, g_direct_equal);
115
116 for (cur = mlist; cur != NULL; cur = cur->next) {
117 msginfo = (MsgInfo *)cur->data;
118 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
119 }
120
121 return msg_table;
122}
123
124gint procmsg_read_cache_data_str(FILE *fp, gchar **str)
125{
126 gchar buf[BUFFSIZE];
127 guint32 len;
128 gchar *tmp = NULL;
129
130 if (fread(&len, sizeof(len), 1, fp) != 1)
131 return -1;
132
133 if (len > G_MAXINT)
134 return -1;
135
136 while (len > 0) {
137 size_t size = MIN(len, BUFFSIZE - 1);
138
139 if (fread(buf, size, 1, fp) != 1) {
140 if (tmp)
141 g_free(tmp);
142 *str = NULL;
143 return -1;
144 }
145
146 buf[size] = '\0';
147 if (tmp) {
148 *str = g_strconcat(tmp, buf, NULL);
149 g_free(tmp);
150 tmp = *str;
151 } else
152 tmp = *str = g_strdup(buf);
153
154 len -= size;
155 }
156
157 return 0;
158}
159
160static gint procmsg_read_cache_data_str_mem(const gchar **p, const gchar *endp, gchar **str)
161{
162 guint32 len;
163
164 if (endp - *p < sizeof(len))
165 return -1;
166
167 memcpy(&len, *p, sizeof(len));
168 *p += sizeof(len);
169 if (len > G_MAXINT || len > endp - *p)
170 return -1;
171
172 if (len > 0) {
173 *str = g_strndup(*p, len);
174 *p += len;
175 }
176
177 return 0;
178}
179
180#define READ_CACHE_DATA(data) \
181{ \
182 if (procmsg_read_cache_data_str_mem(&p, endp, &data) < 0) { \
183 g_warning("Cache data is corrupted\n"); \
184 procmsg_msginfo_free(msginfo); \
185 procmsg_msg_list_free(mlist); \
186 g_mapped_file_free(mapfile); \
187 return NULL; \
188 } \
189}
190
191#define READ_CACHE_DATA_INT(n) \
192{ \
193 if (endp - p < sizeof(guint32)) { \
194 g_warning("Cache data is corrupted\n"); \
195 procmsg_msginfo_free(msginfo); \
196 procmsg_msg_list_free(mlist); \
197 g_mapped_file_free(mapfile); \
198 return NULL; \
199 } else { \
200 guint32 idata; \
201 memcpy(&idata, p, sizeof(idata)); \
202 n = idata; \
203 p += sizeof(guint32); \
204 } \
205}
206
207GSList *procmsg_read_cache(FolderItem *item, gboolean scan_file)
208{
209 GSList *mlist = NULL;
210 GSList *last = NULL;
211 GMappedFile *mapfile;
212 const gchar *filep;
213 gsize file_len;
214 const gchar *p, *endp;
215 MsgInfo *msginfo;
216 MsgFlags default_flags;
217 guint32 num;
218 guint refnum;
219 FolderType type;
220
221 g_return_val_if_fail(item != NULL, NULL);
222 g_return_val_if_fail(item->folder != NULL, NULL);
223 type = FOLDER_TYPE(item->folder);
224
225 default_flags.perm_flags = MSG_NEW|MSG_UNREAD;
226 default_flags.tmp_flags = 0;
227 if (type == F_MH || type == F_IMAP) {
228 if (item->stype == F_QUEUE) {
229 MSG_SET_TMP_FLAGS(default_flags, MSG_QUEUED);
230 } else if (item->stype == F_DRAFT) {
231 MSG_SET_TMP_FLAGS(default_flags, MSG_DRAFT);
232 }
233 }
234 if (type == F_IMAP) {
235 MSG_SET_TMP_FLAGS(default_flags, MSG_IMAP);
236 } else if (type == F_NEWS) {
237 MSG_SET_TMP_FLAGS(default_flags, MSG_NEWS);
238 }
239
240 if (type == F_MH) {
241 gchar *path;
242
243 path = folder_item_get_path(item);
244 if (change_dir(path) < 0) {
245 g_free(path);
246 return NULL;
247 }
248 g_free(path);
249 }
250
251 mapfile = procmsg_open_cache_file_mmap(item, DATA_READ);
252 if (!mapfile) {
253 item->cache_dirty = TRUE;
254 return NULL;
255 }
256
257 debug_print("Reading summary cache...\n");
258
259 filep = g_mapped_file_get_contents(mapfile);
260 file_len = g_mapped_file_get_length(mapfile);
261 endp = filep + file_len;
262 p = filep + sizeof(guint32); /* version */
263
264 while (endp - p >= sizeof(num)) {
265 msginfo = g_new0(MsgInfo, 1);
266
267 READ_CACHE_DATA_INT(msginfo->msgnum);
268
269 READ_CACHE_DATA_INT(msginfo->size);
270 READ_CACHE_DATA_INT(msginfo->mtime);
271 READ_CACHE_DATA_INT(msginfo->date_t);
272 READ_CACHE_DATA_INT(msginfo->flags.tmp_flags);
273
274 READ_CACHE_DATA(msginfo->fromname);
275
276 READ_CACHE_DATA(msginfo->date);
277 READ_CACHE_DATA(msginfo->from);
278 READ_CACHE_DATA(msginfo->to);
279 READ_CACHE_DATA(msginfo->newsgroups);
280 READ_CACHE_DATA(msginfo->subject);
281 READ_CACHE_DATA(msginfo->msgid);
282 READ_CACHE_DATA(msginfo->inreplyto);
283
284 READ_CACHE_DATA_INT(refnum);
285 for (; refnum != 0; refnum--) {
286 gchar *ref;
287
288 READ_CACHE_DATA(ref);
289 msginfo->references =
290 g_slist_prepend(msginfo->references, ref);
291 }
292 if (msginfo->references)
293 msginfo->references =
294 g_slist_reverse(msginfo->references);
295
296 MSG_SET_PERM_FLAGS(msginfo->flags, default_flags.perm_flags);
297 MSG_SET_TMP_FLAGS(msginfo->flags, default_flags.tmp_flags);
298
299 /* if the message file doesn't exist or is changed,
300 don't add the data */
301 if ((type == F_MH && scan_file &&
302 folder_item_is_msg_changed(item, msginfo)) ||
303 msginfo->msgnum == 0) {
304 procmsg_msginfo_free(msginfo);
305 item->cache_dirty = TRUE;
306 } else {
307 msginfo->folder = item;
308
309 if (!mlist)
310 last = mlist = g_slist_append(NULL, msginfo);
311 else {
312 last = g_slist_append(last, msginfo);
313 last = last->next;
314 }
315 }
316 }
317
318 g_mapped_file_free(mapfile);
319
320 if (item->cache_queue) {
321 GSList *qlist;
322 qlist = procmsg_read_cache_queue(item, scan_file);
323 mlist = g_slist_concat(mlist, qlist);
324 }
325
326 debug_print("done.\n");
327
328 return mlist;
329}
330
331#undef READ_CACHE_DATA
332#undef READ_CACHE_DATA_INT
333
334static GSList *procmsg_read_cache_queue(FolderItem *item, gboolean scan_file)
335{
336 FolderType type;
337 MsgInfo *msginfo;
338 MsgFlags default_flags;
339 GSList *cur;
340 GSList *qlist = NULL;
341
342 g_return_val_if_fail(item != NULL, NULL);
343 g_return_val_if_fail(item->folder != NULL, NULL);
344
345 if (!item->cache_queue)
346 return NULL;
347
348 debug_print("Reading cache queue...\n");
349
350 type = FOLDER_TYPE(item->folder);
351 default_flags.perm_flags = MSG_NEW|MSG_UNREAD;
352 default_flags.tmp_flags = 0;
353
354 for (cur = item->cache_queue; cur != NULL; cur = cur->next) {
355 msginfo = (MsgInfo *)cur->data;
356
357 debug_print("read cache queue: %s/%d\n",
358 item->path, msginfo->msgnum);
359
360 MSG_SET_PERM_FLAGS(msginfo->flags, default_flags.perm_flags);
361 MSG_SET_TMP_FLAGS(msginfo->flags, default_flags.tmp_flags);
362
363 if ((type == F_MH && scan_file &&
364 folder_item_is_msg_changed(item, msginfo))) {
365 procmsg_msginfo_free(msginfo);
366 item->cache_dirty = TRUE;
367 } else {
368 msginfo->folder = item;
369 qlist = g_slist_prepend(qlist, msginfo);
370 }
371 }
372
373 g_slist_free(item->cache_queue);
374 item->cache_queue = NULL;
375 item->cache_dirty = TRUE;
376
377 return qlist;
378}
379
380static void mark_unset_new_func(gpointer key, gpointer value, gpointer data)
381{
382 MSG_UNSET_PERM_FLAGS(*((MsgFlags *)value), MSG_NEW);
383}
384
385void procmsg_set_flags(GSList *mlist, FolderItem *item)
386{
387 GSList *cur;
388 gint new = 0, unread = 0, total = 0;
389 gint lastnum = 0;
390 gint unflagged = 0;
391 gboolean mark_queue_exist;
392 MsgInfo *msginfo;
393 GHashTable *mark_table;
394 MsgFlags *flags;
395
396 g_return_if_fail(item != NULL);
397 g_return_if_fail(item->folder != NULL);
398
399 debug_print("Marking the messages...\n");
400
401 mark_queue_exist = (item->mark_queue != NULL);
402 mark_table = procmsg_read_mark_file(item);
403 if (!mark_table) {
404 item->new = item->unread = item->total = g_slist_length(mlist);
405 item->updated = TRUE;
406 item->mark_dirty = TRUE;
407 return;
408 }
409
410 /* unset new flags if new (unflagged) messages exist */
411 if (!mark_queue_exist) {
412 for (cur = mlist; cur != NULL; cur = cur->next) {
413 msginfo = (MsgInfo *)cur->data;
414 flags = g_hash_table_lookup
415 (mark_table, GUINT_TO_POINTER(msginfo->msgnum));
416 if (!flags) {
417 g_hash_table_foreach(mark_table,
418 mark_unset_new_func, NULL);
419 item->mark_dirty = TRUE;
420 break;
421 }
422 }
423 }
424
425 for (cur = mlist; cur != NULL; cur = cur->next) {
426 msginfo = (MsgInfo *)cur->data;
427
428 if (lastnum < msginfo->msgnum)
429 lastnum = msginfo->msgnum;
430
431 flags = g_hash_table_lookup
432 (mark_table, GUINT_TO_POINTER(msginfo->msgnum));
433
434 if (flags != NULL) {
435 /* add the permanent flags only */
436 msginfo->flags.perm_flags = flags->perm_flags;
437 if (MSG_IS_NEW(*flags))
438 ++new;
439 if (MSG_IS_UNREAD(*flags))
440 ++unread;
441 if (FOLDER_TYPE(item->folder) == F_IMAP) {
442 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_IMAP);
443 } else if (FOLDER_TYPE(item->folder) == F_NEWS) {
444 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_NEWS);
445 }
446 } else {
447 ++unflagged;
448 ++new;
449 ++unread;
450 }
451
452 ++total;
453 }
454
455 item->new = new;
456 item->unread = unread;
457 item->total = total;
458 item->unmarked_num = unflagged;
459 item->last_num = lastnum;
460 item->updated = TRUE;
461
462 if (unflagged > 0)
463 item->mark_dirty = TRUE;
464
465 debug_print("new: %d unread: %d unflagged: %d total: %d\n",
466 new, unread, unflagged, total);
467
468 hash_free_value_mem(mark_table);
469 g_hash_table_destroy(mark_table);
470}
471
472static void mark_all_read_func(gpointer key, gpointer value, gpointer data)
473{
474 MSG_UNSET_PERM_FLAGS(*((MsgFlags *)value), MSG_NEW|MSG_UNREAD);
475}
476
477void procmsg_mark_all_read(FolderItem *item)
478{
479 GHashTable *mark_table;
480
481 debug_print("Marking all messages as read\n");
482
483 mark_table = procmsg_read_mark_file(item);
484 if (mark_table) {
485 g_hash_table_foreach(mark_table, mark_all_read_func, NULL);
486 procmsg_write_mark_file(item, mark_table);
487 hash_free_value_mem(mark_table);
488 g_hash_table_destroy(mark_table);
489 }
490
491 if (item->mark_queue) {
492 GSList *cur;
493 MsgFlagInfo *flaginfo;
494
495 for (cur = item->mark_queue; cur != NULL; cur = cur->next) {
496 flaginfo = (MsgFlagInfo *)cur->data;
497 MSG_UNSET_PERM_FLAGS
498 (flaginfo->flags, MSG_NEW|MSG_UNREAD);
499 }
500 item->mark_dirty = TRUE;
501 }
502
503 item->new = item->unread = 0;
504}
505
506static FolderSortType cmp_func_sort_type;
507
508GSList *procmsg_sort_msg_list(GSList *mlist, FolderSortKey sort_key,
509 FolderSortType sort_type)
510{
511 GCompareFunc cmp_func;
512
513 switch (sort_key) {
514 case SORT_BY_MARK:
515 cmp_func = procmsg_cmp_by_mark; break;
516 case SORT_BY_UNREAD:
517 cmp_func = procmsg_cmp_by_unread; break;
518 case SORT_BY_MIME:
519 cmp_func = procmsg_cmp_by_mime; break;
520 case SORT_BY_LABEL:
521 cmp_func = procmsg_cmp_by_label; break;
522 case SORT_BY_NUMBER:
523 cmp_func = procmsg_cmp_by_number; break;
524 case SORT_BY_SIZE:
525 cmp_func = procmsg_cmp_by_size; break;
526 case SORT_BY_DATE:
527 cmp_func = procmsg_cmp_by_date; break;
528 case SORT_BY_FROM:
529 cmp_func = procmsg_cmp_by_from; break;
530 case SORT_BY_SUBJECT:
531 cmp_func = procmsg_cmp_by_subject; break;
532 case SORT_BY_TO:
533 cmp_func = procmsg_cmp_by_to; break;
534 default:
535 return mlist;
536 }
537
538 cmp_func_sort_type = sort_type;
539
540 mlist = g_slist_sort(mlist, cmp_func);
541
542 return mlist;
543}
544
545gint procmsg_get_last_num_in_msg_list(GSList *mlist)
546{
547 GSList *cur;
548 MsgInfo *msginfo;
549 gint last = 0;
550
551 for (cur = mlist; cur != NULL; cur = cur->next) {
552 msginfo = (MsgInfo *)cur->data;
553 if (msginfo && msginfo->msgnum > last)
554 last = msginfo->msgnum;
555 }
556
557 return last;
558}
559
560void procmsg_msg_list_free(GSList *mlist)
561{
562 GSList *cur;
563 MsgInfo *msginfo;
564
565 for (cur = mlist; cur != NULL; cur = cur->next) {
566 msginfo = (MsgInfo *)cur->data;
567 procmsg_msginfo_free(msginfo);
568 }
569 g_slist_free(mlist);
570}
571
572void procmsg_write_cache(MsgInfo *msginfo, FILE *fp)
573{
574 MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
575 GSList *cur;
576
577 WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
578 WRITE_CACHE_DATA_INT(msginfo->size, fp);
579 WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
580 WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
581 WRITE_CACHE_DATA_INT(flags, fp);
582
583 WRITE_CACHE_DATA(msginfo->fromname, fp);
584
585 WRITE_CACHE_DATA(msginfo->date, fp);
586 WRITE_CACHE_DATA(msginfo->from, fp);
587 WRITE_CACHE_DATA(msginfo->to, fp);
588 WRITE_CACHE_DATA(msginfo->newsgroups, fp);
589 WRITE_CACHE_DATA(msginfo->subject, fp);
590 WRITE_CACHE_DATA(msginfo->msgid, fp);
591 WRITE_CACHE_DATA(msginfo->inreplyto, fp);
592
593 WRITE_CACHE_DATA_INT(g_slist_length(msginfo->references), fp);
594 for (cur = msginfo->references; cur != NULL; cur = cur->next) {
595 WRITE_CACHE_DATA((gchar *)cur->data, fp);
596 }
597}
598
599void procmsg_write_flags(MsgInfo *msginfo, FILE *fp)
600{
601 MsgPermFlags flags = msginfo->flags.perm_flags;
602
603 WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
604 WRITE_CACHE_DATA_INT(flags, fp);
605}
606
607void procmsg_write_cache_list(FolderItem *item, GSList *mlist)
608{
609 FILE *fp;
610 GSList *cur;
611
612 g_return_if_fail(item != NULL);
613
614 debug_print("Writing summary cache (%s)\n", item->path);
615
616 fp = procmsg_open_cache_file(item, DATA_WRITE);
617 if (fp == NULL)
618 return;
619
620 for (cur = mlist; cur != NULL; cur = cur->next) {
621 MsgInfo *msginfo = (MsgInfo *)cur->data;
622 procmsg_write_cache(msginfo, fp);
623 }
624
625 if (item->cache_queue)
626 procmsg_flush_cache_queue(item, fp);
627
628 fclose(fp);
629 item->cache_dirty = FALSE;
630}
631
632void procmsg_write_flags_list(FolderItem *item, GSList *mlist)
633{
634 FILE *fp;
635 GSList *cur;
636
637 g_return_if_fail(item != NULL);
638
639 debug_print("Writing summary flags (%s)\n", item->path);
640
641 fp = procmsg_open_mark_file(item, DATA_WRITE);
642 if (fp == NULL)
643 return;
644
645 for (cur = mlist; cur != NULL; cur = cur->next) {
646 MsgInfo *msginfo = (MsgInfo *)cur->data;
647 procmsg_write_flags(msginfo, fp);
648 }
649
650 if (item->mark_queue)
651 procmsg_flush_mark_queue(item, fp);
652
653 fclose(fp);
654 item->mark_dirty = FALSE;
655}
656
657static gint cmp_by_item(gconstpointer a, gconstpointer b)
658{
659 const MsgInfo *msginfo1 = a;
660 const MsgInfo *msginfo2 = b;
661
662 if (msginfo1->folder == msginfo2->folder)
663 return msginfo1->msgnum - msginfo2->msgnum;
664
665 return msginfo1->folder - msginfo2->folder;
666}
667
668void procmsg_write_flags_for_multiple_folders(GSList *mlist)
669{
670 GSList *tmp_list, *cur;
671 FolderItem *prev_item = NULL;
672 FILE *fp = NULL;
673
674 if (!mlist)
675 return;
676
677 tmp_list = g_slist_copy(mlist);
678 tmp_list = g_slist_sort(tmp_list, cmp_by_item);
679
680 for (cur = tmp_list; cur != NULL; cur = cur->next) {
681 MsgInfo *msginfo = (MsgInfo *)cur->data;
682 FolderItem *item = msginfo->folder;
683
684 if (prev_item != item) {
685 if (fp)
686 fclose(fp);
687 fp = procmsg_open_mark_file(item, DATA_APPEND);
688 if (!fp) {
689 g_warning("can't open mark file\n");
690 break;
691 }
692 item->updated = TRUE;
693 }
694 procmsg_write_flags(msginfo, fp);
695 prev_item = item;
696 }
697
698 if (fp)
699 fclose(fp);
700 g_slist_free(tmp_list);
701}
702
703void procmsg_flush_mark_queue(FolderItem *item, FILE *fp)
704{
705 MsgFlagInfo *flaginfo;
706 MsgInfo msginfo = {0};
707 gboolean append = FALSE;
708 GSList *qlist, *cur;
709
710 g_return_if_fail(item != NULL);
711
712 if (!item->mark_queue)
713 return;
714
715 debug_print("flushing mark_queue: %s ...\n", item->path);
716
717 if (!fp) {
718 append = TRUE;
719 fp = procmsg_open_mark_file(item, DATA_APPEND);
720 g_return_if_fail(fp != NULL);
721 }
722
723 qlist = g_slist_reverse(item->mark_queue);
724 item->mark_queue = NULL;
725
726 for (cur = qlist; cur != NULL; cur = cur->next) {
727 flaginfo = (MsgFlagInfo *)cur->data;
728
729 msginfo.msgnum = flaginfo->msgnum;
730 msginfo.flags = flaginfo->flags;
731 procmsg_write_flags(&msginfo, fp);
732 g_free(flaginfo);
733 }
734
735 g_slist_free(qlist);
736
737 if (append)
738 fclose(fp);
739}
740
741void procmsg_add_mark_queue(FolderItem *item, gint num, MsgFlags flags)
742{
743 MsgFlagInfo *flaginfo;
744
745 flaginfo = g_new(MsgFlagInfo, 1);
746 flaginfo->msgnum = num;
747 flaginfo->flags = flags;
748 item->mark_queue = g_slist_prepend(item->mark_queue, flaginfo);
749}
750
751void procmsg_flaginfo_list_free(GSList *flaglist)
752{
753 GSList *cur;
754 MsgFlagInfo *flaginfo;
755
756 for (cur = flaglist; cur != NULL; cur = cur->next) {
757 flaginfo = (MsgFlagInfo *)cur->data;
758 g_free(flaginfo);
759 }
760 g_slist_free(flaglist);
761}
762
763void procmsg_flush_cache_queue(FolderItem *item, FILE *fp)
764{
765 MsgInfo *msginfo;
766 gboolean append = FALSE;
767 GSList *qlist, *cur;
768
769 g_return_if_fail(item != NULL);
770
771 if (!item->cache_queue)
772 return;
773
774 debug_print("flushing cache_queue: %s ...\n", item->path);
775
776 if (!fp) {
777 append = TRUE;
778 fp = procmsg_open_cache_file(item, DATA_APPEND);
779 g_return_if_fail(fp != NULL);
780 }
781
782 qlist = g_slist_reverse(item->cache_queue);
783 item->cache_queue = NULL;
784
785 for (cur = qlist; cur != NULL; cur = cur->next) {
786 msginfo = (MsgInfo *)cur->data;
787
788 debug_print("flush cache queue: %s/%d\n",
789 item->path, msginfo->msgnum);
790 procmsg_write_cache(msginfo, fp);
791 procmsg_msginfo_free(msginfo);
792 }
793
794 g_slist_free(qlist);
795
796 if (append)
797 fclose(fp);
798}
799
800void procmsg_add_cache_queue(FolderItem *item, gint num, MsgInfo *msginfo)
801{
802 MsgInfo *queue_msginfo;
803
804 g_return_if_fail(msginfo != NULL);
805
806 queue_msginfo = procmsg_msginfo_copy(msginfo);
807 queue_msginfo->msgnum = num;
808 queue_msginfo->folder = item;
809 if (queue_msginfo->file_path) {
810 g_free(queue_msginfo->file_path);
811 queue_msginfo->file_path = NULL;
812 }
813
814 debug_print("procmsg_add_cache_queue: add msg cache: %s/%d\n",
815 item->path, num);
816 item->cache_queue = g_slist_prepend(item->cache_queue, queue_msginfo);
817}
818
819gboolean procmsg_flush_folder(FolderItem *item)
820{
821 gboolean flushed = FALSE;
822 gint n_new, n_unread, n_total, n_min, n_max;
823
824 g_return_val_if_fail(item != NULL, FALSE);
825 g_return_val_if_fail(item->folder != NULL, FALSE);
826
827 if (FOLDER_TYPE(item->folder) != F_MH || item->last_num < 0) {
828 folder_item_scan(item);
829 return TRUE;
830 }
831
832 if (item->mark_queue && !item->opened)
833 flushed = TRUE;
834 procmsg_get_mark_sum(item, &n_new, &n_unread, &n_total, &n_min, &n_max,
835 0);
836 item->unmarked_num = 0;
837 item->new = n_new;
838 item->unread = n_unread;
839 item->total = n_total;
840
841 if (item->cache_queue && !item->opened) {
842 procmsg_flush_cache_queue(item, NULL);
843 flushed = TRUE;
844 }
845
846 if (flushed)
847 debug_print("procmsg_flush_folder: flushed %s\n", item->path);
848
849 return flushed;
850}
851
852static void procmsg_flush_folder_foreach_func(gpointer key, gpointer val,
853 gpointer data)
854{
855 procmsg_flush_folder(FOLDER_ITEM(key));
856}
857
858void procmsg_flush_folder_foreach(GHashTable *folder_table)
859{
860 g_hash_table_foreach(folder_table, procmsg_flush_folder_foreach_func,
861 NULL);
862}
863
864void procmsg_add_flags(FolderItem *item, gint num, MsgFlags flags)
865{
866 FILE *fp;
867 MsgInfo msginfo;
868
869 g_return_if_fail(item != NULL);
870
871 if (item->opened) {
872 procmsg_add_mark_queue(item, num, flags);
873 return;
874 }
875
876 if ((fp = procmsg_open_mark_file(item, DATA_APPEND)) == NULL) {
877 g_warning(_("can't open mark file\n"));
878 return;
879 }
880
881 msginfo.msgnum = num;
882 msginfo.flags = flags;
883
884 procmsg_write_flags(&msginfo, fp);
885 fclose(fp);
886}
887
888struct MarkSum {
889 gint *new;
890 gint *unread;
891 gint *total;
892 gint *min;
893 gint *max;
894 gint first;
895};
896
897static void mark_sum_func(gpointer key, gpointer value, gpointer data)
898{
899 MsgFlags *flags = value;
900 gint num = GPOINTER_TO_INT(key);
901 struct MarkSum *marksum = data;
902
903 if (marksum->first <= num) {
904 if (MSG_IS_NEW(*flags)) (*marksum->new)++;
905 if (MSG_IS_UNREAD(*flags)) (*marksum->unread)++;
906 if (num > *marksum->max) *marksum->max = num;
907 if (num < *marksum->min || *marksum->min == 0) *marksum->min = num;
908 (*marksum->total)++;
909 }
910
911 g_free(flags);
912}
913
914void procmsg_get_mark_sum(FolderItem *item,
915 gint *new, gint *unread, gint *total,
916 gint *min, gint *max,
917 gint first)
918{
919 GHashTable *mark_table;
920 struct MarkSum marksum;
921
922 *new = *unread = *total = *min = *max = 0;
923 marksum.new = new;
924 marksum.unread = unread;
925 marksum.total = total;
926 marksum.min = min;
927 marksum.max = max;
928 marksum.first = first;
929
930 mark_table = procmsg_read_mark_file(item);
931
932 if (mark_table) {
933 g_hash_table_foreach(mark_table, mark_sum_func, &marksum);
934 g_hash_table_destroy(mark_table);
935 }
936}
937
938static GHashTable *procmsg_read_mark_file(FolderItem *item)
939{
940 FILE *fp;
941 GHashTable *mark_table = NULL;
942 guint32 idata;
943 guint num;
944 MsgFlags *flags;
945 MsgPermFlags perm_flags;
946 GSList *cur;
947
948 if ((fp = procmsg_open_mark_file(item, DATA_READ)) == NULL)
949 return NULL;
950
951 mark_table = g_hash_table_new(NULL, g_direct_equal);
952
953 while (fread(&idata, sizeof(idata), 1, fp) == 1) {
954 num = idata;
955 if (fread(&idata, sizeof(idata), 1, fp) != 1) break;
956 perm_flags = idata;
957
958 flags = g_hash_table_lookup(mark_table, GUINT_TO_POINTER(num));
959 if (flags != NULL)
960 g_free(flags);
961
962 flags = g_new0(MsgFlags, 1);
963 flags->perm_flags = perm_flags;
964
965 g_hash_table_insert(mark_table, GUINT_TO_POINTER(num), flags);
966 }
967
968 fclose(fp);
969
970 if (item->mark_queue) {
971 g_hash_table_foreach(mark_table, mark_unset_new_func, NULL);
972 item->mark_dirty = TRUE;
973 }
974
975 for (cur = item->mark_queue; cur != NULL; cur = cur->next) {
976 MsgFlagInfo *flaginfo = (MsgFlagInfo *)cur->data;
977
978 flags = g_hash_table_lookup(mark_table,
979 GUINT_TO_POINTER(flaginfo->msgnum));
980 if (flags != NULL)
981 g_free(flags);
982
983 flags = g_new0(MsgFlags, 1);
984 flags->perm_flags = flaginfo->flags.perm_flags;
985
986 g_hash_table_insert(mark_table,
987 GUINT_TO_POINTER(flaginfo->msgnum), flags);
988
989 }
990
991 if (item->mark_queue && !item->opened) {
992 procmsg_write_mark_file(item, mark_table);
993 procmsg_flaginfo_list_free(item->mark_queue);
994 item->mark_queue = NULL;
995 item->mark_dirty = FALSE;
996 }
997
998 return mark_table;
999}
1000
1001static void write_mark_func(gpointer key, gpointer value, gpointer data)
1002{
1003 MsgInfo msginfo;
1004
1005 msginfo.msgnum = GPOINTER_TO_UINT(key);
1006 msginfo.flags.perm_flags = ((MsgFlags *)value)->perm_flags;
1007 procmsg_write_flags(&msginfo, (FILE *)data);
1008}
1009
1010static void procmsg_write_mark_file(FolderItem *item, GHashTable *mark_table)
1011{
1012 FILE *fp;
1013
1014 if ((fp = procmsg_open_mark_file(item, DATA_WRITE)) == NULL) {
1015 g_warning("procmsg_write_mark_file: cannot open mark file.");
1016 return;
1017 }
1018 g_hash_table_foreach(mark_table, write_mark_func, fp);
1019 fclose(fp);
1020}
1021
1022FILE *procmsg_open_data_file(const gchar *file, guint version,
1023 DataOpenMode mode, gchar *buf, size_t buf_size)
1024{
1025 FILE *fp;
1026 guint32 data_ver = 0;
1027
1028 g_return_val_if_fail(file != NULL, NULL);
1029
1030 if (mode == DATA_WRITE) {
1031 if ((fp = g_fopen(file, "wb")) == NULL) {
1032 if (errno == EACCES) {
1033 change_file_mode_rw(NULL, file);
1034 if ((fp = g_fopen(file, "wb")) == NULL) {
1035 FILE_OP_ERROR(file, "procmsg_open_data_file: fopen");
1036 return NULL;
1037 }
1038 } else {
1039 FILE_OP_ERROR(file, "procmsg_open_data_file: fopen");
1040 return NULL;
1041 }
1042 }
1043 if (change_file_mode_rw(fp, file) < 0)
1044 FILE_OP_ERROR(file, "chmod");
1045
1046 WRITE_CACHE_DATA_INT(version, fp);
1047 return fp;
1048 }
1049
1050 /* check version */
1051 if ((fp = g_fopen(file, "rb")) == NULL) {
1052 if (errno == EACCES) {
1053 change_file_mode_rw(NULL, file);
1054 if ((fp = g_fopen(file, "rb")) == NULL) {
1055 FILE_OP_ERROR(file, "procmsg_open_data_file: fopen");
1056 }
1057 } else {
1058 debug_print("Mark/Cache file '%s' not found\n", file);
1059 }
1060 }
1061
1062 if (fp) {
1063 if (buf && buf_size > 0)
1064 setvbuf(fp, buf, _IOFBF, buf_size);
1065 if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1) {
1066 g_warning("%s: cannot read mark/cache file (truncated?)\n", file);
1067 fclose(fp);
1068 fp = NULL;
1069 } else if (version != data_ver) {
1070 g_message("%s: Mark/Cache version is different (%u != %u). Discarding it.\n",
1071 file, data_ver, version);
1072 fclose(fp);
1073 fp = NULL;
1074 }
1075 }
1076
1077 if (mode == DATA_READ)
1078 return fp;
1079
1080 if (fp) {
1081 /* reopen with append mode */
1082 fclose(fp);
1083 if ((fp = g_fopen(file, "ab")) == NULL) {
1084 if (errno == EACCES) {
1085 change_file_mode_rw(NULL, file);
1086 if ((fp = g_fopen(file, "ab")) == NULL) {
1087 FILE_OP_ERROR(file, "procmsg_open_data_file: fopen");
1088 }
1089 } else {
1090 FILE_OP_ERROR(file, "procmsg_open_data_file: fopen");
1091 }
1092 }
1093 } else {
1094 /* open with overwrite mode if mark file doesn't exist or
1095 version is different */
1096 fp = procmsg_open_data_file(file, version, DATA_WRITE, buf,
1097 buf_size);
1098 }
1099
1100 return fp;
1101}
1102
1103static GMappedFile *procmsg_open_cache_file_mmap(FolderItem *item,
1104 DataOpenMode mode)
1105{
1106 gchar *cachefile;
1107 GMappedFile *map = NULL;
1108 GError *error = NULL;
1109 gsize size;
1110 guint32 data_ver = 0;
1111 gchar *p;
1112
1113 if (mode != DATA_READ)
1114 return NULL;
1115
1116 cachefile = folder_item_get_cache_file(item);
1117 if (cachefile) {
1118 map = g_mapped_file_new(cachefile, FALSE, &error);
1119 if (!map) {
1120 if (error && error->code == G_FILE_ERROR_NOENT)
1121 debug_print("%s: mark/cache file not found\n", cachefile);
1122 else if (error)
1123 g_warning("%s: cannot open mark/cache file: %s", cachefile, error->message);
1124 else
1125 g_warning("%s: cannot open mark/cache file", cachefile);
1126 if (error)
1127 g_error_free(error);
1128 g_free(cachefile);
1129 return NULL;
1130 }
1131 size = g_mapped_file_get_length(map);
1132 if (size < sizeof(data_ver)) {
1133 g_warning("%s: cannot read mark/cache file (truncated?)", cachefile);
1134 g_mapped_file_free(map);
1135 g_free(cachefile);
1136 return NULL;
1137 }
1138 p = g_mapped_file_get_contents(map);
1139 data_ver = *(guint32 *)p;
1140 if (CACHE_VERSION != data_ver) {
1141 g_message("%s: Mark/Cache version is different (%u != %u). Discarding it.\n",
1142 cachefile, data_ver, CACHE_VERSION);
1143 g_mapped_file_free(map);
1144 g_free(cachefile);
1145 return NULL;
1146 }
1147 g_free(cachefile);
1148 }
1149
1150 return map;
1151}
1152
1153FILE *procmsg_open_cache_file(FolderItem *item, DataOpenMode mode)
1154{
1155 gchar *cachefile;
1156 FILE *fp;
1157
1158 cachefile = folder_item_get_cache_file(item);
1159 fp = procmsg_open_data_file(cachefile, CACHE_VERSION, mode, NULL, 0);
1160 g_free(cachefile);
1161
1162 return fp;
1163}
1164
1165FILE *procmsg_open_mark_file(FolderItem *item, DataOpenMode mode)
1166{
1167 gchar *markfile;
1168 FILE *fp;
1169
1170 markfile = folder_item_get_mark_file(item);
1171 fp = procmsg_open_data_file(markfile, MARK_VERSION, mode, NULL, 0);
1172 g_free(markfile);
1173
1174 return fp;
1175}
1176
1177void procmsg_clear_cache(FolderItem *item)
1178{
1179 FILE *fp;
1180
1181 fp = procmsg_open_cache_file(item, DATA_WRITE);
1182 if (fp)
1183 fclose(fp);
1184}
1185
1186void procmsg_clear_mark(FolderItem *item)
1187{
1188 FILE *fp;
1189
1190 fp = procmsg_open_mark_file(item, DATA_WRITE);
1191 if (fp)
1192 fclose(fp);
1193}
1194
1195/* return the reversed thread tree */
1196GNode *procmsg_get_thread_tree(GSList *mlist)
1197{
1198 GNode *root, *parent, *node, *next;
1199 GHashTable *table;
1200 MsgInfo *msginfo;
1201 const gchar *msgid;
1202 GSList *reflist;
1203
1204 root = g_node_new(NULL);
1205 table = g_hash_table_new(g_str_hash, g_str_equal);
1206
1207 for (; mlist != NULL; mlist = mlist->next) {
1208 msginfo = (MsgInfo *)mlist->data;
1209 parent = root;
1210
1211 /* only look for the real parent first */
1212 if (msginfo->inreplyto) {
1213 parent = g_hash_table_lookup(table, msginfo->inreplyto);
1214 if (parent == NULL)
1215 parent = root;
1216 }
1217
1218 node = g_node_insert_data_before
1219 (parent, parent == root ? parent->children : NULL,
1220 msginfo);
1221 if ((msgid = msginfo->msgid) &&
1222 g_hash_table_lookup(table, msgid) == NULL)
1223 g_hash_table_insert(table, (gchar *)msgid, node);
1224 }
1225
1226 /* complete the unfinished threads */
1227 for (node = root->children; node != NULL; ) {
1228 next = node->next;
1229 msginfo = (MsgInfo *)node->data;
1230 parent = NULL;
1231
1232 if (msginfo->inreplyto)
1233 parent = g_hash_table_lookup(table, msginfo->inreplyto);
1234
1235 /* try looking for the indirect parent */
1236 if (!parent && msginfo->references) {
1237 for (reflist = msginfo->references;
1238 reflist != NULL; reflist = reflist->next)
1239 if ((parent = g_hash_table_lookup
1240 (table, reflist->data)) != NULL)
1241 break;
1242 }
1243
1244 /* node should not be the parent, and node should not
1245 be an ancestor of parent (circular reference) */
1246 if (parent && parent != node &&
1247 !g_node_is_ancestor(node, parent)) {
1248 g_node_unlink(node);
1249 g_node_insert_before
1250 (parent, parent->children, node);
1251 }
1252 node = next;
1253 }
1254
1255 g_hash_table_destroy(table);
1256
1257 return root;
1258}
1259
1260static gboolean procmsg_thread_date_func(GNode *node, gpointer data)
1261{
1262 guint *tdate = (guint *)data;
1263 MsgInfo *msginfo = (MsgInfo *)node->data;
1264
1265 if (*tdate < msginfo->date_t)
1266 *tdate = msginfo->date_t;
1267
1268 return FALSE;
1269}
1270
1271guint procmsg_get_thread_date(GNode *node)
1272{
1273 guint tdate = 0;
1274
1275 g_return_val_if_fail(node != NULL && node->parent != NULL &&
1276 node->parent->parent == NULL, 0);
1277
1278 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1279 procmsg_thread_date_func, &tdate);
1280
1281 return tdate;
1282}
1283
1284gint procmsg_move_messages(GSList *mlist)
1285{
1286 GSList *cur, *movelist = NULL;
1287 MsgInfo *msginfo;
1288 FolderItem *dest = NULL;
1289 GHashTable *hash;
1290 gint val = 0;
1291
1292 if (!mlist) return 0;
1293
1294 hash = procmsg_to_folder_hash_table_create(mlist);
1295 folder_item_scan_foreach(hash);
1296 g_hash_table_destroy(hash);
1297
1298 for (cur = mlist; cur != NULL; cur = cur->next) {
1299 msginfo = (MsgInfo *)cur->data;
1300 if (!dest) {
1301 dest = msginfo->to_folder;
1302 movelist = g_slist_append(movelist, msginfo);
1303 } else if (dest == msginfo->to_folder) {
1304 movelist = g_slist_append(movelist, msginfo);
1305 } else {
1306 val = folder_item_move_msgs(dest, movelist);
1307 g_slist_free(movelist);
1308 movelist = NULL;
1309 if (val == -1)
1310 return val;
1311 dest = msginfo->to_folder;
1312 movelist = g_slist_append(movelist, msginfo);
1313 }
1314 }
1315
1316 if (movelist) {
1317 val = folder_item_move_msgs(dest, movelist);
1318 g_slist_free(movelist);
1319 }
1320
1321 return val == -1 ? -1 : 0;
1322}
1323
1324gint procmsg_copy_messages(GSList *mlist)
1325{
1326 GSList *cur, *copylist = NULL;
1327 MsgInfo *msginfo;
1328 FolderItem *dest = NULL;
1329 GHashTable *hash;
1330 gint val = 0;
1331
1332 if (!mlist) return 0;
1333
1334 hash = procmsg_to_folder_hash_table_create(mlist);
1335 folder_item_scan_foreach(hash);
1336 g_hash_table_destroy(hash);
1337
1338 for (cur = mlist; cur != NULL; cur = cur->next) {
1339 msginfo = (MsgInfo *)cur->data;
1340 if (!dest) {
1341 dest = msginfo->to_folder;
1342 copylist = g_slist_append(copylist, msginfo);
1343 } else if (dest == msginfo->to_folder) {
1344 copylist = g_slist_append(copylist, msginfo);
1345 } else {
1346 val = folder_item_copy_msgs(dest, copylist);
1347 g_slist_free(copylist);
1348 copylist = NULL;
1349 if (val == -1)
1350 return val;
1351 dest = msginfo->to_folder;
1352 copylist = g_slist_append(copylist, msginfo);
1353 }
1354 }
1355
1356 if (copylist) {
1357 val = folder_item_copy_msgs(dest, copylist);
1358 g_slist_free(copylist);
1359 }
1360
1361 return val == -1 ? -1 : 0;
1362}
1363
1364gint procmsg_add_messages_from_queue(FolderItem *dest, GSList *mlist,
1365 gboolean is_move)
1366{
1367 MsgInfo *msginfo;
1368 GSList *cur;
1369 gchar *file;
1370 FILE *fp;
1371 gchar buf[BUFFSIZE];
1372 gchar *dest_file;
1373 gboolean is_error = FALSE;
1374 FolderItem *src;
1375 MsgFlags flags;
1376
1377 g_return_val_if_fail(dest != NULL, -1);
1378 g_return_val_if_fail(mlist != NULL, -1);
1379
1380 msginfo = (MsgInfo *)mlist->data;
1381 if (!msginfo || !msginfo->folder || msginfo->folder->stype != F_QUEUE ||
1382 !MSG_IS_QUEUED(msginfo->flags) || dest->stype == F_QUEUE)
1383 return -1;
1384
1385 debug_print("procmsg_add_messages_from_queue: adding messages from queue folder\n");
1386
1387 for (cur = mlist; cur != NULL; cur = cur->next) {
1388 msginfo = (MsgInfo *)cur->data;
1389 flags = msginfo->flags;
1390 if (!MSG_IS_QUEUED(flags))
1391 return -1;
1392 MSG_UNSET_TMP_FLAGS(flags, MSG_QUEUED);
1393 src = msginfo->folder;
1394 file = procmsg_get_message_file(msginfo);
1395 if (!file)
1396 return -1;
1397 if ((fp = g_fopen(file, "rb")) == NULL) {
1398 FILE_OP_ERROR(file, "folder_item_move_msgs: fopen");
1399 g_free(file);
1400 return -1;
1401 }
1402 while (fgets(buf, sizeof(buf), fp) != NULL) {
1403 if (buf[0] == '\r' || buf[0] == '\n')
1404 break;
1405 }
1406 if (ferror(fp)) {
1407 fclose(fp);
1408 g_free(file);
1409 return -1;
1410 }
1411
1412 dest_file = get_tmp_file();
1413 debug_print("copy queued msg: %s -> %s\n", file, dest_file);
1414
1415 if (copy_file_part(fp, ftell(fp), G_MAXINT, dest_file) < 0) {
1416 fclose(fp);
1417 is_error = TRUE;
1418 } else {
1419 fclose(fp);
1420 if (folder_item_add_msg(dest, dest_file, &flags, TRUE) < 0) {
1421 g_unlink(dest_file);
1422 is_error = TRUE;
1423 } else if (is_move &&
1424 folder_item_remove_msg(src, msginfo) < 0)
1425 is_error = TRUE;
1426 }
1427
1428 g_free(dest_file);
1429 g_free(file);
1430 if (is_error)
1431 return -1;
1432 }
1433
1434 return 0;
1435}
1436
1437gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
1438{
1439 gchar *path, *file;
1440
1441 g_return_val_if_fail(msginfo != NULL, NULL);
1442
1443 if (msginfo->encinfo && msginfo->encinfo->plaintext_file)
1444 file = g_strdup(msginfo->encinfo->plaintext_file);
1445 else if (msginfo->file_path)
1446 return g_strdup(msginfo->file_path);
1447 else {
1448 gchar nstr[16];
1449 path = folder_item_get_path(msginfo->folder);
1450 file = g_strconcat(path, G_DIR_SEPARATOR_S,
1451 utos_buf(nstr, msginfo->msgnum), NULL);
1452 g_free(path);
1453 }
1454
1455 return file;
1456}
1457
1458gchar *procmsg_get_message_file(MsgInfo *msginfo)
1459{
1460 gchar *filename = NULL;
1461
1462 g_return_val_if_fail(msginfo != NULL, NULL);
1463
1464 if (msginfo->file_path)
1465 return g_strdup(msginfo->file_path);
1466
1467 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
1468 if (!filename)
1469 debug_print(_("can't fetch message %d\n"), msginfo->msgnum);
1470
1471 return filename;
1472}
1473
1474GSList *procmsg_get_message_file_list(GSList *mlist)
1475{
1476 GSList *file_list = NULL;
1477 MsgInfo *msginfo;
1478 MsgFileInfo *fileinfo;
1479 gchar *file;
1480
1481 while (mlist != NULL) {
1482 msginfo = (MsgInfo *)mlist->data;
1483 file = procmsg_get_message_file(msginfo);
1484 if (!file) {
1485 procmsg_message_file_list_free(file_list);
1486 return NULL;
1487 }
1488 fileinfo = g_new(MsgFileInfo, 1);
1489 fileinfo->file = file;
1490 fileinfo->flags = g_new(MsgFlags, 1);
1491 *fileinfo->flags = msginfo->flags;
1492 file_list = g_slist_prepend(file_list, fileinfo);
1493 mlist = mlist->next;
1494 }
1495
1496 file_list = g_slist_reverse(file_list);
1497
1498 return file_list;
1499}
1500
1501void procmsg_message_file_list_free(GSList *file_list)
1502{
1503 GSList *cur;
1504 MsgFileInfo *fileinfo;
1505
1506 for (cur = file_list; cur != NULL; cur = cur->next) {
1507 fileinfo = (MsgFileInfo *)cur->data;
1508 g_free(fileinfo->file);
1509 g_free(fileinfo->flags);
1510 g_free(fileinfo);
1511 }
1512
1513 g_slist_free(file_list);
1514}
1515
1516FILE *procmsg_open_message(MsgInfo *msginfo)
1517{
1518 FILE *fp;
1519 gchar *file;
1520
1521 g_return_val_if_fail(msginfo != NULL, NULL);
1522
1523 file = procmsg_get_message_file_path(msginfo);
1524 g_return_val_if_fail(file != NULL, NULL);
1525
1526 if (!is_file_exist(file)) {
1527 g_free(file);
1528 file = procmsg_get_message_file(msginfo);
1529 if (!file)
1530 return NULL;
1531 }
1532
1533 if ((fp = g_fopen(file, "rb")) == NULL) {
1534 FILE_OP_ERROR(file, "procmsg_open_message: fopen");
1535 g_free(file);
1536 return NULL;
1537 }
1538
1539 g_free(file);
1540
1541 if (MSG_IS_QUEUED(msginfo->flags)) {
1542 gchar buf[BUFFSIZE];
1543
1544 while (fgets(buf, sizeof(buf), fp) != NULL)
1545 if (buf[0] == '\r' || buf[0] == '\n') break;
1546 }
1547
1548 return fp;
1549}
1550
1551static DecryptMessageFunc decrypt_message_func = NULL;
1552static gboolean auto_decrypt = TRUE;
1553
1554void procmsg_set_decrypt_message_func(DecryptMessageFunc func)
1555{
1556 decrypt_message_func = func;
1557}
1558
1559void procmsg_set_auto_decrypt_message(gboolean enabled)
1560{
1561 auto_decrypt = enabled;
1562}
1563
1564FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
1565{
1566 FILE *fp;
1567
1568 if (decrypt_message_func && auto_decrypt)
1569 return decrypt_message_func(msginfo, mimeinfo);
1570
1571 *mimeinfo = NULL;
1572 if ((fp = procmsg_open_message(msginfo)) == NULL)
1573 return NULL;
1574 *mimeinfo = procmime_scan_mime_header(fp);
1575
1576 return fp;
1577}
1578
1579gboolean procmsg_msg_exist(MsgInfo *msginfo)
1580{
1581 gchar *path;
1582 gboolean ret;
1583
1584 if (!msginfo) return FALSE;
1585
1586 path = folder_item_get_path(msginfo->folder);
1587 change_dir(path);
1588 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
1589 g_free(path);
1590
1591 return ret;
1592}
1593
1594gboolean procmsg_trash_messages_exist(void)
1595{
1596 FolderItem *trash;
1597 GList *cur;
1598
1599 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
1600 trash = FOLDER(cur->data)->trash;
1601 if (trash && trash->total > 0)
1602 return TRUE;
1603 }
1604
1605 return FALSE;
1606}
1607
1608void procmsg_empty_trash(FolderItem *trash)
1609{
1610 if (!trash)
1611 return;
1612
1613 g_return_if_fail(trash->stype == F_TRASH || trash->stype == F_JUNK);
1614
1615 if (trash->total > 0) {
1616 debug_print("Emptying messages in %s ...\n", trash->path);
1617
1618 folder_item_remove_all_msg(trash);
1619 procmsg_clear_cache(trash);
1620 procmsg_clear_mark(trash);
1621 trash->cache_dirty = FALSE;
1622 trash->mark_dirty = FALSE;
1623 }
1624}
1625
1626void procmsg_empty_all_trash(void)
1627{
1628 FolderItem *trash;
1629 GList *cur;
1630
1631 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
1632 trash = FOLDER(cur->data)->trash;
1633 procmsg_empty_trash(trash);
1634 }
1635}
1636
1637static gboolean remove_all_cached_messages_func(GNode *node, gpointer data)
1638{
1639 FolderItem *item;
1640 gchar *dir;
1641
1642 g_return_val_if_fail(node->data != NULL, FALSE);
1643
1644 item = FOLDER_ITEM(node->data);
1645 if (!item->path || item->stype == F_VIRTUAL)
1646 return FALSE;
1647
1648 dir = folder_item_get_path(item);
1649 if (is_dir_exist(dir)) {
1650 debug_print("removing all cached messages in '%s' ...\n",
1651 item->path);
1652 remove_all_numbered_files(dir);
1653 }
1654 g_free(dir);
1655
1656 return FALSE;
1657}
1658
1659void procmsg_remove_all_cached_messages(Folder *folder)
1660{
1661 g_return_if_fail(folder != NULL);
1662 g_return_if_fail(FOLDER_IS_REMOTE(folder));
1663
1664 debug_print("Removing all caches in the mailbox '%s' ...\n",
1665 folder->name);
1666
1667 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1668 remove_all_cached_messages_func, NULL);
1669}
1670
1671gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file)
1672{
1673 gint num;
1674 MsgFlags flag = {0, 0};
1675
1676 debug_print("saving sent message...\n");
1677
1678 if (!outbox)
1679 outbox = folder_get_default_outbox();
1680 g_return_val_if_fail(outbox != NULL, -1);
1681
1682 folder_item_scan(outbox);
1683 if ((num = folder_item_add_msg(outbox, file, &flag, FALSE)) < 0) {
1684 g_warning("can't save message\n");
1685 return -1;
1686 }
1687 procmsg_flush_folder(outbox);
1688
1689 return 0;
1690}
1691
1692static guint print_id = 0;
1693
1694static gint print_command_exec(const gchar *file, const gchar *cmdline)
1695{
1696 static const gchar *def_cmd = "lpr %s";
1697 gchar buf[1024];
1698
1699#ifdef G_OS_WIN32
1700 if (canonicalize_file_replace(file) < 0)
1701 return -1;
1702#endif
1703
1704 if (cmdline && str_find_format_times(cmdline, 's') == 1)
1705 g_snprintf(buf, sizeof(buf) - 1, cmdline, file);
1706 else {
1707 if (cmdline) {
1708 g_warning(_("Print command line is invalid: `%s'\n"),
1709 cmdline);
1710 return -1;
1711 }
1712
1713#ifdef G_OS_WIN32
1714 execute_print_file(file);
1715 return 0;
1716#else
1717 g_snprintf(buf, sizeof(buf) - 1, def_cmd, file);
1718#endif
1719 }
1720
1721 g_strchomp(buf);
1722 if (buf[strlen(buf) - 1] != '&')
1723 strcat(buf, "&");
1724
1725 if (system(buf) != 0)
1726 return -1;
1727
1728 return 0;
1729}
1730
1731static void procmsg_write_headers(MsgInfo *msginfo, MimeInfo *partinfo,
1732 FILE *fp, FILE *dest_fp,
1733 const gchar *encoding, gboolean all_headers)
1734{
1735 GPtrArray *headers;
1736 gint i;
1737
1738 if (all_headers)
1739 headers = procheader_get_header_array_asis(fp, NULL);
1740 else
1741 headers = procheader_get_header_array_for_display(fp, NULL);
1742
1743 for (i = 0; i < headers->len; i++) {
1744 Header *hdr;
1745 gchar *file_str;
1746 const gchar *body;
1747
1748 hdr = g_ptr_array_index(headers, i);
1749
1750 if (partinfo) {
1751 if (!g_ascii_strcasecmp(hdr->name, "Subject") ||
1752 !g_ascii_strcasecmp(hdr->name, "From") ||
1753 !g_ascii_strcasecmp(hdr->name, "To") ||
1754 !g_ascii_strcasecmp(hdr->name, "Cc")) {
1755 unfold_line(hdr->body);
1756 }
1757
1758 body = hdr->body;
1759 while (g_ascii_isspace(*body))
1760 body++;
1761 } else {
1762 if (!g_ascii_strcasecmp(hdr->name, "Subject"))
1763 body = msginfo->subject;
1764 else if (!g_ascii_strcasecmp(hdr->name, "From"))
1765 body = msginfo->from;
1766 else if (!g_ascii_strcasecmp(hdr->name, "To"))
1767 body = msginfo->to;
1768 else if (!g_ascii_strcasecmp(hdr->name, "Cc")) {
1769 unfold_line(hdr->body);
1770 body = hdr->body;
1771 while (g_ascii_isspace(*body))
1772 body++;
1773 } else {
1774 body = hdr->body;
1775 while (g_ascii_isspace(*body))
1776 body++;
1777 }
1778 }
1779
1780 if (body && *body != '\0') {
1781 file_str = conv_codeset_strdup
1782 (body, CS_INTERNAL, encoding);
1783 fprintf(dest_fp, "%s: %s\n", hdr->name,
1784 file_str ? file_str : body);
1785 g_free(file_str);
1786 } else {
1787 fprintf(dest_fp, "%s: (none)\n", hdr->name);
1788 }
1789 }
1790
1791 procheader_header_array_destroy(headers);
1792}
1793
1794void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline,
1795 gboolean all_headers)
1796{
1797 gchar *prtmp;
1798
1799 g_return_if_fail(msginfo != NULL);
1800
1801 prtmp = g_strdup_printf("%s%cprinttmp-%08x.txt",
1802 get_mime_tmp_dir(), G_DIR_SEPARATOR,
1803 print_id++);
1804
1805 if (procmsg_save_message_as_text(msginfo, prtmp,
1806 conv_get_locale_charset_str(),
1807 all_headers) == 0)
1808 print_command_exec(prtmp, cmdline);
1809
1810 g_free(prtmp);
1811}
1812
1813void procmsg_print_message_part(MsgInfo *msginfo, MimeInfo *partinfo,
1814 const gchar *cmdline, gboolean all_headers)
1815{
1816 FILE *msgfp, *tmpfp, *prfp;
1817 gchar *prtmp;
1818 gchar buf[BUFFSIZE];
1819
1820 if ((msgfp = procmsg_open_message(msginfo)) == NULL) {
1821 return;
1822 }
1823
1824 if ((tmpfp = procmime_get_text_content
1825 (partinfo, msgfp, conv_get_locale_charset_str())) == NULL) {
1826 fclose(msgfp);
1827 return;
1828 }
1829 fclose(msgfp);
1830
1831 prtmp = g_strdup_printf("%s%cprinttmp-%08x.txt",
1832 get_mime_tmp_dir(), G_DIR_SEPARATOR,
1833 print_id++);
1834 if ((prfp = g_fopen(prtmp, "w")) == NULL) {
1835 FILE_OP_ERROR(prtmp, "procmsg_print_message_part: fopen");
1836 g_free(prtmp);
1837 fclose(tmpfp);
1838 return;
1839 }
1840
1841 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
1842 fputs(buf, prfp);
1843
1844 fclose(prfp);
1845 fclose(tmpfp);
1846
1847 print_command_exec(prtmp, cmdline);
1848
1849 g_free(prtmp);
1850}
1851
1852gint procmsg_save_message_as_text(MsgInfo *msginfo, const gchar *dest,
1853 const gchar *encoding, gboolean all_headers)
1854{
1855 MimeInfo *mimeinfo, *partinfo;
1856 FILE *fp;
1857 FILE *tmpfp;
1858 FILE *destfp;
1859 gchar buf[BUFFSIZE];
1860 gchar *part_str;
1861 gint ret = 0;
1862
1863 g_return_val_if_fail(msginfo != NULL, -1);
1864 g_return_val_if_fail(dest != NULL, -1);
1865
1866 mimeinfo = procmime_scan_message(msginfo);
1867 if (!mimeinfo)
1868 return -1;
1869 if ((fp = procmsg_open_message(msginfo)) == NULL) {
1870 procmime_mimeinfo_free_all(mimeinfo);
1871 return -1;
1872 }
1873 if ((destfp = g_fopen(dest, "w")) == NULL) {
1874 fclose(fp);
1875 procmime_mimeinfo_free_all(mimeinfo);
1876 return -1;
1877 }
1878 procmsg_write_headers(msginfo, mimeinfo, fp, destfp, encoding, all_headers);
1879 fputc('\n', destfp);
1880
1881 partinfo = mimeinfo;
1882
1883 while (partinfo != NULL) {
1884 if (fseek(fp, partinfo->fpos, SEEK_SET) < 0)
1885 break;
1886
1887 if (partinfo->filename || partinfo->name)
1888 g_snprintf(buf, sizeof(buf), "\n[%s %s (%s)]\n",
1889 partinfo->filename ? partinfo->filename :
1890 partinfo->name,
1891 partinfo->content_type,
1892 to_human_readable(partinfo->content_size));
1893 else
1894 g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n",
1895 partinfo->content_type,
1896 to_human_readable(partinfo->content_size));
1897 part_str = conv_codeset_strdup(buf, CS_INTERNAL, encoding);
1898 if (!part_str)
1899 part_str = g_strdup(buf);
1900
1901 if (partinfo->mime_type == MIME_TEXT ||
1902 partinfo->mime_type == MIME_TEXT_HTML) {
1903 if (!partinfo->main &&
1904 partinfo->parent &&
1905 partinfo->parent->children != partinfo) {
1906 fputs(part_str, destfp);
1907 }
1908
1909 if ((tmpfp = procmime_get_text_content(partinfo, fp, encoding)) == NULL) {
1910 g_free(part_str);
1911 break;
1912 }
1913 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
1914 fputs(buf, destfp);
1915
1916 fclose(tmpfp);
1917 } else if (partinfo->mime_type == MIME_MESSAGE_RFC822) {
1918 fputs(part_str, destfp);
1919 while (fgets(buf, sizeof(buf), fp) != NULL)
1920 if (buf[0] == '\r' || buf[0] == '\n') break;
1921 procmsg_write_headers(msginfo, partinfo, fp, destfp, encoding, all_headers);
1922 fputc('\n', destfp);
1923 } else if (partinfo->mime_type != MIME_MULTIPART) {
1924 fputs(part_str, destfp);
1925 }
1926
1927 g_free(part_str);
1928
1929 if (partinfo->parent && partinfo->parent->content_type &&
1930 !g_ascii_strcasecmp(partinfo->parent->content_type,
1931 "multipart/alternative"))
1932 partinfo = partinfo->parent->next;
1933 else
1934 partinfo = procmime_mimeinfo_next(partinfo);
1935 }
1936
1937 if (fclose(destfp) == EOF) {
1938 FILE_OP_ERROR(dest, "fclose");
1939 g_unlink(dest);
1940 ret = -1;
1941 }
1942
1943 fclose(fp);
1944 procmime_mimeinfo_free_all(mimeinfo);
1945
1946 return ret;
1947}
1948
1949/**
1950 * procmsg_concat_partial_messages:
1951 * @mlist: list of MsgInfo* including message/partial messages.
1952 * @file: output file name of concatenated message.
1953 *
1954 * Concatenate @mlist which consists of message/partial messages and
1955 * output to @file. If @mlist has different partial id, the first one
1956 * is used.
1957 *
1958 * Return value: 0 on success, or -1 if failed.
1959 **/
1960gint procmsg_concat_partial_messages(GSList *mlist, const gchar *file)
1961{
1962 static HeaderEntry hentry[] = {{"Content-Type:", NULL, FALSE},
1963 {NULL, NULL, FALSE}};
1964 FILE *fp;
1965 gchar buf[BUFFSIZE];
1966 FILE *tmp_fp;
1967 gchar *part_id = NULL;
1968 gint total = 0;
1969 MsgInfo *msg_array[100] = {NULL};
1970 MsgInfo *msginfo;
1971 MimeInfo *mimeinfo;
1972 GSList *cur;
1973 gint i;
1974
1975 g_return_val_if_fail(mlist != NULL, -1);
1976 g_return_val_if_fail(file != NULL, -1);
1977
1978 debug_print("procmsg_concat_partial_messages\n");
1979
1980 for (cur = mlist; cur != NULL; cur = cur->next) {
1981 gint n = 0;
1982 gint t = 0;
1983 gchar *cur_id = NULL;
1984
1985 msginfo = (MsgInfo *)cur->data;
1986
1987 fp = procmsg_open_message_decrypted(msginfo, &mimeinfo);
1988 if (!fp)
1989 continue;
1990 if (!mimeinfo->content_type ||
1991 g_ascii_strcasecmp(mimeinfo->content_type, "message/partial") != 0)
1992 goto skip;
1993
1994 rewind(fp);
1995 if (procheader_get_one_field(buf, sizeof(buf), fp, hentry) == -1)
1996 goto skip;
1997
1998 procmime_scan_content_type_partial(buf + strlen(hentry[0].name),
1999 &t, &cur_id, &n);
2000 if (n == 0 || n > 100 || t > 100 || (t > 0 && n > t)) {
2001 debug_print("bad partial number (%d/%d), skip\n", n, t);
2002 g_free(cur_id);
2003 goto skip;
2004 }
2005
2006 debug_print("partial: %d/%d id=%s\n", n, t, cur_id);
2007 if (!part_id)
2008 part_id = g_strdup(cur_id);
2009 if (total == 0)
2010 total = t;
2011
2012 if ((t > 0 && total != t) || (total > 0 && n > total) ||
2013 strcmp(part_id, cur_id) != 0) {
2014 debug_print("skip\n");
2015 g_free(cur_id);
2016 goto skip;
2017 }
2018
2019 msg_array[n - 1] = msginfo;
2020
2021 g_free(cur_id);
2022skip:
2023 procmime_mimeinfo_free_all(mimeinfo);
2024 fclose(fp);
2025 }
2026
2027 if (!part_id) {
2028 debug_print("piece not found\n");
2029 return -1;
2030 }
2031
2032 debug_print("part_id = %s , total = %d\n", part_id, total);
2033 g_free(part_id);
2034
2035 if (total == 0) {
2036 debug_print("total number not found\n");
2037 return -1;
2038 }
2039
2040 /* check if all pieces exist */
2041 for (i = 0; i < total; i++) {
2042 if (msg_array[i] == NULL) {
2043 debug_print("message part %d not exist\n", i + 1);
2044 return -1;
2045 }
2046 }
2047
2048 /* concatenate parts */
2049 if ((tmp_fp = g_fopen(file, "wb")) == NULL) {
2050 FILE_OP_ERROR(file, "fopen");
2051 return -1;
2052 }
2053
2054 for (i = 0; i < total; i++) {
2055 msginfo = msg_array[i];
2056 off_t out_size;
2057 gint empty_line_size = 0;
2058
2059 fp = procmsg_open_message_decrypted(msginfo, &mimeinfo);
2060 if (!fp) {
2061 g_warning("cannot open message part %d\n", i + 1);
2062 fclose(tmp_fp);
2063 g_unlink(file);
2064 return -1;
2065 }
2066
2067 /* write out first headers */
2068 if (i == 0) {
2069 rewind(fp);
2070 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
2071 if (!g_ascii_strncasecmp(buf, "Content-", 8) ||
2072 !g_ascii_strncasecmp(buf, "Subject", 7) ||
2073 !g_ascii_strncasecmp(buf, "Message-ID", 10) ||
2074 !g_ascii_strncasecmp(buf, "Encrypted", 9) ||
2075 !g_ascii_strncasecmp(buf, "MIME-Version", 12))
2076 continue;
2077 fputs(buf, tmp_fp);
2078 fputs("\n", tmp_fp);
2079 }
2080
2081 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
2082 if (!g_ascii_strncasecmp(buf, "Content-", 8) ||
2083 !g_ascii_strncasecmp(buf, "Subject", 7) ||
2084 !g_ascii_strncasecmp(buf, "Message-ID", 10) ||
2085 !g_ascii_strncasecmp(buf, "Encrypted", 9) ||
2086 !g_ascii_strncasecmp(buf, "MIME-Version", 12)) {
2087 fputs(buf, tmp_fp);
2088 fputs("\n", tmp_fp);
2089 }
2090 }
2091
2092 /* header-body separator */
2093 fputs("\n", tmp_fp);
2094 }
2095
2096 out_size = get_left_file_size(fp);
2097 if (out_size < 0) {
2098 g_warning("cannot tell left file size of part %d\n", i + 1);
2099 procmime_mimeinfo_free_all(mimeinfo);
2100 fclose(fp);
2101 fclose(tmp_fp);
2102 g_unlink(file);
2103 return -1;
2104 }
2105 empty_line_size = get_last_empty_line_size(fp, out_size);
2106 if (empty_line_size < 0) {
2107 g_warning("cannot get last empty line size of part %d\n", i + 1);
2108 procmime_mimeinfo_free_all(mimeinfo);
2109 fclose(fp);
2110 fclose(tmp_fp);
2111 g_unlink(file);
2112 return -1;
2113 }
2114
2115 if (append_file_part(fp, ftell(fp), out_size - empty_line_size,
2116 tmp_fp) < 0) {
2117 g_warning("write failed\n");
2118 procmime_mimeinfo_free_all(mimeinfo);
2119 fclose(fp);
2120 fclose(tmp_fp);
2121 g_unlink(file);
2122 return -1;
2123 }
2124
2125 procmime_mimeinfo_free_all(mimeinfo);
2126 fclose(fp);
2127 }
2128
2129 fclose(tmp_fp);
2130
2131 return 0;
2132}
2133
2134static gboolean procmsg_get_flags(FolderItem *item, gint num,
2135 MsgPermFlags *flags)
2136{
2137 FILE *fp;
2138 guint32 idata;
2139 gint read_num;
2140 MsgPermFlags perm_flags;
2141 gboolean found = FALSE;
2142 GSList *cur;
2143
2144 if ((fp = procmsg_open_mark_file(item, DATA_READ)) == NULL)
2145 return FALSE;
2146
2147 while (fread(&idata, sizeof(idata), 1, fp) == 1) {
2148 read_num = idata;
2149 if (fread(&idata, sizeof(idata), 1, fp) != 1)
2150 break;
2151 perm_flags = idata;
2152 if (read_num == num) {
2153 *flags = perm_flags;
2154 found = TRUE;
2155 break;
2156 }
2157 }
2158
2159 fclose(fp);
2160 if (found)
2161 return TRUE;
2162
2163 for (cur = item->mark_queue; cur != NULL; cur = cur->next) {
2164 MsgFlagInfo *flaginfo = (MsgFlagInfo *)cur->data;
2165
2166 if (flaginfo->msgnum == num) {
2167 *flags = flaginfo->flags.perm_flags;
2168 found = TRUE;
2169 break;
2170 }
2171 }
2172
2173 return found;
2174}
2175
2176MsgInfo *procmsg_get_msginfo(FolderItem *item, gint num)
2177{
2178 MsgInfo *msginfo;
2179 FolderType type;
2180
2181 g_return_val_if_fail(item->folder != NULL, NULL);
2182
2183 msginfo = folder_item_get_msginfo(item, num);
2184 if (!msginfo)
2185 return NULL;
2186
2187 type = FOLDER_TYPE(item->folder);
2188 if (type == F_MH || type == F_IMAP) {
2189 if (item->stype == F_QUEUE) {
2190 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
2191 } else if (item->stype == F_DRAFT) {
2192 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
2193 }
2194 }
2195 if (type == F_IMAP) {
2196 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_IMAP);
2197 } else if (type == F_NEWS) {
2198 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_NEWS);
2199 }
2200
2201 if (type == F_MH || type == F_NEWS) {
2202 MsgPermFlags flags = 0;
2203 if (procmsg_get_flags(item, num, &flags))
2204 msginfo->flags.perm_flags = flags;
2205 }
2206
2207 return msginfo;
2208}
2209
2210MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
2211{
2212 MsgInfo *newmsginfo;
2213
2214 if (msginfo == NULL) return NULL;
2215
2216 newmsginfo = g_new0(MsgInfo, 1);
2217
2218#define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
2219#define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
2220 g_strdup(msginfo->mmb) : NULL
2221
2222 MEMBCOPY(msgnum);
2223 MEMBCOPY(size);
2224 MEMBCOPY(mtime);
2225 MEMBCOPY(date_t);
2226
2227 MEMBCOPY(flags);
2228
2229 MEMBDUP(fromname);
2230
2231 MEMBDUP(date);
2232 MEMBDUP(from);
2233 MEMBDUP(to);
2234 MEMBDUP(cc);
2235 MEMBDUP(newsgroups);
2236 MEMBDUP(subject);
2237 MEMBDUP(msgid);
2238 MEMBDUP(inreplyto);
2239
2240 MEMBCOPY(folder);
2241 MEMBCOPY(to_folder);
2242
2243 MEMBDUP(xface);
2244
2245 MEMBDUP(file_path);
2246
2247 if (msginfo->encinfo) {
2248 newmsginfo->encinfo = g_new0(MsgEncryptInfo, 1);
2249 MEMBDUP(encinfo->plaintext_file);
2250 MEMBDUP(encinfo->sigstatus);
2251 MEMBDUP(encinfo->sigstatus_full);
2252 MEMBCOPY(encinfo->decryption_failed);
2253 }
2254
2255 return newmsginfo;
2256}
2257
2258MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
2259{
2260 MsgInfo *full_msginfo;
2261 gchar *file;
2262
2263 if (msginfo == NULL) return NULL;
2264
2265 file = procmsg_get_message_file(msginfo);
2266 if (!file) {
2267 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
2268 return NULL;
2269 }
2270
2271 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE);
2272 g_free(file);
2273 if (!full_msginfo) return NULL;
2274
2275 full_msginfo->msgnum = msginfo->msgnum;
2276 full_msginfo->size = msginfo->size;
2277 full_msginfo->mtime = msginfo->mtime;
2278 full_msginfo->folder = msginfo->folder;
2279 full_msginfo->to_folder = msginfo->to_folder;
2280
2281 full_msginfo->file_path = g_strdup(msginfo->file_path);
2282
2283 if (msginfo->encinfo) {
2284 full_msginfo->encinfo = g_new0(MsgEncryptInfo, 1);
2285 full_msginfo->encinfo->plaintext_file =
2286 g_strdup(msginfo->encinfo->plaintext_file);
2287 full_msginfo->encinfo->sigstatus =
2288 g_strdup(msginfo->encinfo->sigstatus);
2289 full_msginfo->encinfo->sigstatus_full =
2290 g_strdup(msginfo->encinfo->sigstatus_full);
2291 full_msginfo->encinfo->decryption_failed =
2292 msginfo->encinfo->decryption_failed;
2293 }
2294
2295 return full_msginfo;
2296}
2297
2298gboolean procmsg_msginfo_equal(MsgInfo *msginfo_a, MsgInfo *msginfo_b)
2299{
2300 if (!msginfo_a || !msginfo_b)
2301 return FALSE;
2302
2303 if (msginfo_a == msginfo_b)
2304 return TRUE;
2305
2306 if (msginfo_a->folder == msginfo_b->folder &&
2307 msginfo_a->msgnum == msginfo_b->msgnum &&
2308 msginfo_a->size == msginfo_b->size &&
2309 msginfo_a->mtime == msginfo_b->mtime)
2310 return TRUE;
2311
2312 return FALSE;
2313}
2314
2315void procmsg_msginfo_free(MsgInfo *msginfo)
2316{
2317 if (msginfo == NULL) return;
2318
2319 g_free(msginfo->xface);
2320
2321 g_free(msginfo->fromname);
2322
2323 g_free(msginfo->date);
2324 g_free(msginfo->from);
2325 g_free(msginfo->to);
2326 g_free(msginfo->cc);
2327 g_free(msginfo->newsgroups);
2328 g_free(msginfo->subject);
2329 g_free(msginfo->msgid);
2330 g_free(msginfo->inreplyto);
2331
2332 slist_free_strings(msginfo->references);
2333 g_slist_free(msginfo->references);
2334
2335 g_free(msginfo->file_path);
2336
2337 if (msginfo->encinfo) {
2338 g_free(msginfo->encinfo->plaintext_file);
2339 g_free(msginfo->encinfo->sigstatus);
2340 g_free(msginfo->encinfo->sigstatus_full);
2341 g_free(msginfo->encinfo);
2342 }
2343
2344 g_free(msginfo);
2345}
2346
2347gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
2348{
2349 const MsgInfo *msginfo1 = a;
2350 const MsgInfo *msginfo2 = b;
2351
2352 if (!msginfo1 || !msginfo2)
2353 return 0;
2354
2355 return msginfo1->msgnum - msginfo2->msgnum;
2356}
2357
2358#define CMP_FUNC_DEF(func_name, val) \
2359static gint func_name(gconstpointer a, gconstpointer b) \
2360{ \
2361 const MsgInfo *msginfo1 = a; \
2362 const MsgInfo *msginfo2 = b; \
2363 gint ret; \
2364 \
2365 if (!msginfo1 || !msginfo2) \
2366 return 0; \
2367 \
2368 ret = (val); \
2369 if (ret == 0) \
2370 ret = msginfo1->date_t - msginfo2->date_t; \
2371 \
2372 return ret * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \
2373}
2374
2375CMP_FUNC_DEF(procmsg_cmp_by_mark,
2376 MSG_IS_MARKED(msginfo1->flags) - MSG_IS_MARKED(msginfo2->flags))
2377CMP_FUNC_DEF(procmsg_cmp_by_unread,
2378 MSG_IS_UNREAD(msginfo1->flags) - MSG_IS_UNREAD(msginfo2->flags))
2379CMP_FUNC_DEF(procmsg_cmp_by_mime,
2380 MSG_IS_MIME(msginfo1->flags) - MSG_IS_MIME(msginfo2->flags))
2381CMP_FUNC_DEF(procmsg_cmp_by_label,
2382 MSG_GET_COLORLABEL(msginfo1->flags) -
2383 MSG_GET_COLORLABEL(msginfo2->flags))
2384CMP_FUNC_DEF(procmsg_cmp_by_size, msginfo1->size - msginfo2->size)
2385
2386#undef CMP_FUNC_DEF
2387#define CMP_FUNC_DEF(func_name, val) \
2388static gint func_name(gconstpointer a, gconstpointer b) \
2389{ \
2390 const MsgInfo *msginfo1 = a; \
2391 const MsgInfo *msginfo2 = b; \
2392 \
2393 if (!msginfo1 || !msginfo2) \
2394 return 0; \
2395 \
2396 return (val) * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \
2397}
2398
2399CMP_FUNC_DEF(procmsg_cmp_by_number, msginfo1->msgnum - msginfo2->msgnum)
2400CMP_FUNC_DEF(procmsg_cmp_by_date, msginfo1->date_t - msginfo2->date_t)
2401
2402#undef CMP_FUNC_DEF
2403#define CMP_FUNC_DEF(func_name, var_name) \
2404static gint func_name(gconstpointer a, gconstpointer b) \
2405{ \
2406 const MsgInfo *msginfo1 = a; \
2407 const MsgInfo *msginfo2 = b; \
2408 gint ret; \
2409 \
2410 if (!msginfo1->var_name) \
2411 return (msginfo2->var_name != NULL) * \
2412 (cmp_func_sort_type == SORT_ASCENDING ? -1 : 1);\
2413 if (!msginfo2->var_name) \
2414 return (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \
2415 \
2416 ret = g_ascii_strcasecmp \
2417 (msginfo1->var_name, msginfo2->var_name); \
2418 if (ret == 0) \
2419 ret = msginfo1->date_t - msginfo2->date_t; \
2420 \
2421 return ret * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \
2422}
2423
2424CMP_FUNC_DEF(procmsg_cmp_by_from, fromname)
2425CMP_FUNC_DEF(procmsg_cmp_by_to, to)
2426
2427#undef CMP_FUNC_DEF
2428
2429static gint procmsg_cmp_by_subject(gconstpointer a, gconstpointer b)
2430{
2431 const MsgInfo *msginfo1 = a;
2432 const MsgInfo *msginfo2 = b;
2433 gint ret;
2434
2435 if (!msginfo1->subject)
2436 return (msginfo2->subject != NULL) *
2437 (cmp_func_sort_type == SORT_ASCENDING ? -1 : 1);
2438 if (!msginfo2->subject)
2439 return (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1);
2440
2441 ret = subject_compare_for_sort(msginfo1->subject, msginfo2->subject);
2442 if (ret == 0)
2443 ret = msginfo1->date_t - msginfo2->date_t;
2444
2445 return ret * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1);
2446}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __PROCMSG_H__
21#define __PROCMSG_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28#include <stdio.h>
29#include <time.h>
30#include <sys/types.h>
31#include <string.h>
32
33typedef struct _MsgInfo MsgInfo;
34typedef struct _MsgFlags MsgFlags;
35typedef struct _MsgFileInfo MsgFileInfo;
36typedef struct _MsgEncryptInfo MsgEncryptInfo;
37
38#include "folder.h"
39#include "procmime.h"
40#include "utils.h"
41
42typedef enum
43{
44 DATA_READ,
45 DATA_WRITE,
46 DATA_APPEND
47} DataOpenMode;
48
49#define MSG_NEW (1U << 0)
50#define MSG_UNREAD (1U << 1)
51#define MSG_MARKED (1U << 2)
52#define MSG_DELETED (1U << 3)
53#define MSG_REPLIED (1U << 4)
54#define MSG_FORWARDED (1U << 5)
55
56#define MSG_CLABEL_SBIT (7) /* start bit of color label */
57#define MAKE_MSG_CLABEL(h, m, l) (((h) << (MSG_CLABEL_SBIT + 2)) | \
58 ((m) << (MSG_CLABEL_SBIT + 1)) | \
59 ((l) << (MSG_CLABEL_SBIT + 0)))
60
61#define MSG_CLABEL_NONE MAKE_MSG_CLABEL(0U, 0U, 0U)
62#define MSG_CLABEL_1 MAKE_MSG_CLABEL(0U, 0U, 1U)
63#define MSG_CLABEL_2 MAKE_MSG_CLABEL(0U, 1U, 0U)
64#define MSG_CLABEL_3 MAKE_MSG_CLABEL(0U, 1U, 1U)
65#define MSG_CLABEL_4 MAKE_MSG_CLABEL(1U, 0U, 0U)
66#define MSG_CLABEL_5 MAKE_MSG_CLABEL(1U, 0U, 1U)
67#define MSG_CLABEL_6 MAKE_MSG_CLABEL(1U, 1U, 0U)
68#define MSG_CLABEL_7 MAKE_MSG_CLABEL(1U, 1U, 1U)
69
70#define MSG_CLABEL_ORANGE MSG_CLABEL_1
71#define MSG_CLABEL_RED MSG_CLABEL_2
72#define MSG_CLABEL_PINK MSG_CLABEL_3
73#define MSG_CLABEL_SKYBLUE MSG_CLABEL_4
74#define MSG_CLABEL_BLUE MSG_CLABEL_5
75#define MSG_CLABEL_GREEN MSG_CLABEL_6
76#define MSG_CLABEL_BROWN MSG_CLABEL_7
77
78/* RESERVED */
79#define MSG_RESERVED (1U << 31)
80
81#define MSG_CLABEL_FLAG_MASK (MSG_CLABEL_7)
82
83typedef guint32 MsgPermFlags;
84
85#define MSG_MOVE (1U << 0)
86#define MSG_COPY (1U << 1)
87#define MSG_QUEUED (1U << 16)
88#define MSG_DRAFT (1U << 17)
89#define MSG_ENCRYPTED (1U << 18)
90#define MSG_IMAP (1U << 19)
91#define MSG_NEWS (1U << 20)
92#define MSG_SIGNED (1U << 21)
93#define MSG_MIME_HTML (1U << 26)
94#define MSG_FLAG_CHANGED (1U << 27)
95#define MSG_CACHED (1U << 28)
96#define MSG_MIME (1U << 29)
97#define MSG_INVALID (1U << 30)
98#define MSG_RECEIVED (1U << 31)
99
100#define MSG_CACHED_FLAG_MASK (MSG_MIME|MSG_MIME_HTML)
101
102typedef guint32 MsgTmpFlags;
103
104#define MSG_SET_FLAGS(msg, flags) { (msg) |= (flags); }
105#define MSG_UNSET_FLAGS(msg, flags) { (msg) &= ~(flags); }
106#define MSG_SET_PERM_FLAGS(msg, flags) \
107 MSG_SET_FLAGS((msg).perm_flags, flags)
108#define MSG_SET_TMP_FLAGS(msg, flags) \
109 MSG_SET_FLAGS((msg).tmp_flags, flags)
110#define MSG_UNSET_PERM_FLAGS(msg, flags) \
111 MSG_UNSET_FLAGS((msg).perm_flags, flags)
112#define MSG_UNSET_TMP_FLAGS(msg, flags) \
113 MSG_UNSET_FLAGS((msg).tmp_flags, flags)
114
115#define MSG_IS_NEW(msg) (((msg).perm_flags & MSG_NEW) != 0)
116#define MSG_IS_UNREAD(msg) (((msg).perm_flags & MSG_UNREAD) != 0)
117#define MSG_IS_MARKED(msg) (((msg).perm_flags & MSG_MARKED) != 0)
118#define MSG_IS_DELETED(msg) (((msg).perm_flags & MSG_DELETED) != 0)
119#define MSG_IS_REPLIED(msg) (((msg).perm_flags & MSG_REPLIED) != 0)
120#define MSG_IS_FORWARDED(msg) (((msg).perm_flags & MSG_FORWARDED) != 0)
121
122#define MSG_GET_COLORLABEL(msg) (((msg).perm_flags & MSG_CLABEL_FLAG_MASK))
123#define MSG_GET_COLORLABEL_VALUE(msg) (MSG_GET_COLORLABEL(msg) >> MSG_CLABEL_SBIT)
124#define MSG_SET_COLORLABEL_VALUE(msg, val) \
125 MSG_SET_PERM_FLAGS(msg, ((((guint)(val)) & 7) << MSG_CLABEL_SBIT))
126
127#define MSG_IS_MOVE(msg) (((msg).tmp_flags & MSG_MOVE) != 0)
128#define MSG_IS_COPY(msg) (((msg).tmp_flags & MSG_COPY) != 0)
129
130#define MSG_IS_QUEUED(msg) (((msg).tmp_flags & MSG_QUEUED) != 0)
131#define MSG_IS_DRAFT(msg) (((msg).tmp_flags & MSG_DRAFT) != 0)
132#define MSG_IS_ENCRYPTED(msg) (((msg).tmp_flags & MSG_ENCRYPTED) != 0)
133#define MSG_IS_IMAP(msg) (((msg).tmp_flags & MSG_IMAP) != 0)
134#define MSG_IS_NEWS(msg) (((msg).tmp_flags & MSG_NEWS) != 0)
135#define MSG_IS_SIGNED(msg) (((msg).tmp_flags & MSG_SIGNED) != 0)
136#define MSG_IS_MIME_HTML(msg) (((msg).tmp_flags & MSG_MIME_HTML) != 0)
137#define MSG_IS_FLAG_CHANGED(msg) (((msg).tmp_flags & MSG_FLAG_CHANGED) != 0)
138#define MSG_IS_CACHED(msg) (((msg).tmp_flags & MSG_CACHED) != 0)
139#define MSG_IS_MIME(msg) (((msg).tmp_flags & MSG_MIME) != 0)
140#define MSG_IS_INVALID(msg) (((msg).tmp_flags & MSG_INVALID) != 0)
141#define MSG_IS_RECEIVED(msg) (((msg).tmp_flags & MSG_RECEIVED) != 0)
142
143#define WRITE_CACHE_DATA_INT(n, fp) \
144{ \
145 guint32 idata; \
146 \
147 idata = (guint32)n; \
148 fwrite(&idata, sizeof(idata), 1, fp); \
149}
150
151#define WRITE_CACHE_DATA(data, fp) \
152{ \
153 size_t len; \
154 \
155 if (data == NULL) { \
156 len = 0; \
157 WRITE_CACHE_DATA_INT(len, fp); \
158 } else { \
159 len = strlen(data); \
160 WRITE_CACHE_DATA_INT(len, fp); \
161 if (len > 0) \
162 fwrite(data, len, 1, fp); \
163 } \
164}
165
166struct _MsgFlags
167{
168 MsgPermFlags perm_flags;
169 MsgTmpFlags tmp_flags;
170};
171
172struct _MsgInfo
173{
174 guint msgnum;
175 gsize size;
176 stime_t mtime;
177 stime_t date_t;
178
179 MsgFlags flags;
180
181 gchar *fromname;
182
183 gchar *date;
184 gchar *from;
185 gchar *to;
186 gchar *cc;
187 gchar *newsgroups;
188 gchar *subject;
189 gchar *msgid;
190 gchar *inreplyto;
191
192 GSList *references;
193
194 FolderItem *folder;
195 FolderItem *to_folder;
196
197 gchar *xface;
198
199 /* used only for temporary messages */
200 gchar *file_path;
201
202 /* used only for encrypted (and signed) messages */
203 MsgEncryptInfo *encinfo;
204};
205
206struct _MsgFileInfo
207{
208 gchar *file;
209 MsgFlags *flags;
210};
211
212struct _MsgEncryptInfo
213{
214 gchar *plaintext_file;
215 gchar *sigstatus;
216 gchar *sigstatus_full;
217 gboolean decryption_failed;
218};
219
220typedef FILE * (*DecryptMessageFunc) (MsgInfo *msginfo,
221 MimeInfo **mimeinfo);
222
223GHashTable *procmsg_msg_hash_table_create (GSList *mlist);
224void procmsg_msg_hash_table_append (GHashTable *msg_table,
225 GSList *mlist);
226GHashTable *procmsg_to_folder_hash_table_create (GSList *mlist);
227
228gint procmsg_read_cache_data_str (FILE *fp,
229 gchar **str);
230
231GSList *procmsg_read_cache (FolderItem *item,
232 gboolean scan_file);
233void procmsg_set_flags (GSList *mlist,
234 FolderItem *item);
235void procmsg_mark_all_read (FolderItem *item);
236GSList *procmsg_sort_msg_list (GSList *mlist,
237 FolderSortKey sort_key,
238 FolderSortType sort_type);
239gint procmsg_get_last_num_in_msg_list(GSList *mlist);
240void procmsg_msg_list_free (GSList *mlist);
241
242void procmsg_write_cache (MsgInfo *msginfo,
243 FILE *fp);
244void procmsg_write_flags (MsgInfo *msginfo,
245 FILE *fp);
246void procmsg_write_cache_list (FolderItem *item,
247 GSList *mlist);
248void procmsg_write_flags_list (FolderItem *item,
249 GSList *mlist);
250void procmsg_write_flags_for_multiple_folders
251 (GSList *mlist);
252
253void procmsg_flaginfo_list_free (GSList *flaglist);
254
255void procmsg_flush_mark_queue (FolderItem *item,
256 FILE *fp);
257void procmsg_add_mark_queue (FolderItem *item,
258 gint num,
259 MsgFlags flags);
260void procmsg_flush_cache_queue (FolderItem *item,
261 FILE *fp);
262void procmsg_add_cache_queue (FolderItem *item,
263 gint num,
264 MsgInfo *msginfo);
265
266gboolean procmsg_flush_folder (FolderItem *item);
267void procmsg_flush_folder_foreach (GHashTable *folder_table);
268
269void procmsg_add_flags (FolderItem *item,
270 gint num,
271 MsgFlags flags);
272
273void procmsg_get_mark_sum (FolderItem *item,
274 gint *new,
275 gint *unread,
276 gint *total,
277 gint *min,
278 gint *max,
279 gint first);
280
281FILE *procmsg_open_data_file (const gchar *file,
282 guint version,
283 DataOpenMode mode,
284 gchar *buf,
285 size_t buf_size);
286
287FILE *procmsg_open_cache_file (FolderItem *item,
288 DataOpenMode mode);
289FILE *procmsg_open_mark_file (FolderItem *item,
290 DataOpenMode mode);
291
292void procmsg_clear_cache (FolderItem *item);
293void procmsg_clear_mark (FolderItem *item);
294
295GNode *procmsg_get_thread_tree (GSList *mlist);
296guint procmsg_get_thread_date (GNode *node);
297
298gint procmsg_move_messages (GSList *mlist);
299gint procmsg_copy_messages (GSList *mlist);
300
301gint procmsg_add_messages_from_queue (FolderItem *dest,
302 GSList *mlist,
303 gboolean is_move);
304
305gchar *procmsg_get_message_file_path (MsgInfo *msginfo);
306gchar *procmsg_get_message_file (MsgInfo *msginfo);
307GSList *procmsg_get_message_file_list (GSList *mlist);
308void procmsg_message_file_list_free (GSList *file_list);
309FILE *procmsg_open_message (MsgInfo *msginfo);
310
311void procmsg_set_decrypt_message_func (DecryptMessageFunc func);
312void procmsg_set_auto_decrypt_message (gboolean enabled);
313FILE *procmsg_open_message_decrypted (MsgInfo *msginfo,
314 MimeInfo **mimeinfo);
315
316gboolean procmsg_msg_exist (MsgInfo *msginfo);
317
318gboolean procmsg_trash_messages_exist (void);
319void procmsg_empty_trash (FolderItem *trash);
320void procmsg_empty_all_trash (void);
321
322void procmsg_remove_all_cached_messages
323 (Folder *folder);
324
325gint procmsg_save_to_outbox (FolderItem *outbox,
326 const gchar *file);
327void procmsg_print_message (MsgInfo *msginfo,
328 const gchar *cmdline,
329 gboolean all_headers);
330void procmsg_print_message_part (MsgInfo *msginfo,
331 MimeInfo *partinfo,
332 const gchar *cmdline,
333 gboolean all_headers);
334
335gint procmsg_save_message_as_text (MsgInfo *msginfo,
336 const gchar *dest,
337 const gchar *encoding,
338 gboolean all_headers);
339
340gint procmsg_concat_partial_messages (GSList *mlist,
341 const gchar *file);
342
343MsgInfo *procmsg_get_msginfo (FolderItem *item,
344 gint num);
345
346MsgInfo *procmsg_msginfo_copy (MsgInfo *msginfo);
347MsgInfo *procmsg_msginfo_get_full_info (MsgInfo *msginfo);
348gboolean procmsg_msginfo_equal (MsgInfo *msginfo_a,
349 MsgInfo *msginfo_b);
350void procmsg_msginfo_free (MsgInfo *msginfo);
351
352gint procmsg_cmp_msgnum_for_sort (gconstpointer a,
353 gconstpointer b);
354
355#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include <glib.h>
21#include <ctype.h>
22
23static gboolean get_hex_value(guchar *out, gchar c1, gchar c2);
24static void get_hex_str(gchar *out, guchar ch);
25
26#define MAX_LINELEN 76
27
28#define IS_LBREAK(p) \
29 (*(p) == '\0' || *(p) == '\n' || (*(p) == '\r' && *((p) + 1) == '\n'))
30
31#define SOFT_LBREAK_IF_REQUIRED(n) \
32 if (len + (n) > MAX_LINELEN || \
33 (len + (n) == MAX_LINELEN && (!IS_LBREAK(inp + 1)))) { \
34 *outp++ = '='; \
35 *outp++ = '\n'; \
36 len = 0; \
37 }
38
39void qp_encode_line(gchar *out, const guchar *in)
40{
41 const guchar *inp = in;
42 gchar *outp = out;
43 guchar ch;
44 gint len = 0;
45
46 while (*inp != '\0') {
47 ch = *inp;
48
49 if (IS_LBREAK(inp)) {
50 *outp++ = '\n';
51 len = 0;
52 if (*inp == '\r')
53 inp++;
54 inp++;
55 } else if (ch == '\t' || ch == ' ') {
56 if (IS_LBREAK(inp + 1)) {
57 SOFT_LBREAK_IF_REQUIRED(3);
58 *outp++ = '=';
59 get_hex_str(outp, ch);
60 outp += 2;
61 len += 3;
62 inp++;
63 } else {
64 SOFT_LBREAK_IF_REQUIRED(1);
65 *outp++ = *inp++;
66 len++;
67 }
68 } else if ((ch >= 33 && ch <= 60) || (ch >= 62 && ch <= 126)) {
69 SOFT_LBREAK_IF_REQUIRED(1);
70 *outp++ = *inp++;
71 len++;
72 } else {
73 SOFT_LBREAK_IF_REQUIRED(3);
74 *outp++ = '=';
75 get_hex_str(outp, ch);
76 outp += 2;
77 len += 3;
78 inp++;
79 }
80 }
81
82 if (len > 0)
83 *outp++ = '\n';
84
85 *outp = '\0';
86}
87
88gint qp_decode_line(gchar *str)
89{
90 gchar *inp = str, *outp = str;
91
92 while (*inp != '\0') {
93 if (*inp == '=') {
94 if (inp[1] && inp[2] &&
95 get_hex_value((guchar *)outp, inp[1], inp[2])
96 == TRUE) {
97 inp += 3;
98 } else if (inp[1] == '\0' || g_ascii_isspace(inp[1])) {
99 /* soft line break */
100 break;
101 } else {
102 /* broken QP string */
103 *outp = *inp++;
104 }
105 } else {
106 *outp = *inp++;
107 }
108 outp++;
109 }
110
111 *outp = '\0';
112
113 return outp - str;
114}
115
116gint qp_decode_q_encoding(guchar *out, const gchar *in, gint inlen)
117{
118 const gchar *inp = in;
119 guchar *outp = out;
120
121 if (inlen < 0)
122 inlen = G_MAXINT;
123
124 while (inp - in < inlen && *inp != '\0') {
125 if (*inp == '=' && inp + 3 - in <= inlen) {
126 if (get_hex_value(outp, inp[1], inp[2]) == TRUE) {
127 inp += 3;
128 } else {
129 *outp = *inp++;
130 }
131 } else if (*inp == '_') {
132 *outp = ' ';
133 inp++;
134 } else {
135 *outp = *inp++;
136 }
137 outp++;
138 }
139
140 *outp = '\0';
141
142 return outp - out;
143}
144
145gint qp_get_q_encoding_len(const guchar *str)
146{
147 const guchar *inp = str;
148 gint len = 0;
149
150 while (*inp != '\0') {
151 if (*inp == 0x20)
152 len++;
153 else if (*inp == '=' || *inp == '?' || *inp == '_' ||
154 *inp < 32 || *inp > 127 || g_ascii_isspace(*inp))
155 len += 3;
156 else
157 len++;
158
159 inp++;
160 }
161
162 return len;
163}
164
165void qp_q_encode(gchar *out, const guchar *in)
166{
167 const guchar *inp = in;
168 gchar *outp = out;
169
170 while (*inp != '\0') {
171 if (*inp == 0x20)
172 *outp++ = '_';
173 else if (*inp == '=' || *inp == '?' || *inp == '_' ||
174 *inp < 32 || *inp > 127 || g_ascii_isspace(*inp)) {
175 *outp++ = '=';
176 get_hex_str(outp, *inp);
177 outp += 2;
178 } else
179 *outp++ = *inp;
180
181 inp++;
182 }
183
184 *outp = '\0';
185}
186
187#define HEX_TO_INT(val, hex) \
188{ \
189 gchar c = hex; \
190 \
191 if ('0' <= c && c <= '9') { \
192 val = c - '0'; \
193 } else if ('a' <= c && c <= 'f') { \
194 val = c - 'a' + 10; \
195 } else if ('A' <= c && c <= 'F') { \
196 val = c - 'A' + 10; \
197 } else { \
198 val = -1; \
199 } \
200}
201
202static gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
203{
204 gint hi, lo;
205
206 HEX_TO_INT(hi, c1);
207 HEX_TO_INT(lo, c2);
208
209 if (hi == -1 || lo == -1)
210 return FALSE;
211
212 *out = (hi << 4) + lo;
213 return TRUE;
214}
215
216#define INT_TO_HEX(hex, val) \
217{ \
218 if ((val) < 10) \
219 hex = '0' + (val); \
220 else \
221 hex = 'A' + (val) - 10; \
222}
223
224static void get_hex_str(gchar *out, guchar ch)
225{
226 gchar hex;
227
228 INT_TO_HEX(hex, ch >> 4);
229 *out++ = hex;
230 INT_TO_HEX(hex, ch & 0x0f);
231 *out++ = hex;
232}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __QUOTED_PRINTABLE_H__
21#define __QUOTED_PRINTABLE_H__
22
23#include <glib.h>
24
25void qp_encode_line (gchar *out,
26 const guchar *in);
27gint qp_decode_line (gchar *str);
28
29gint qp_decode_q_encoding (guchar *out,
30 const gchar *in,
31 gint inlen);
32gint qp_get_q_encoding_len (const guchar *str);
33void qp_q_encode (gchar *out,
34 const guchar *in);
35
36#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2014 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <stdio.h>
29#include <string.h>
30#include <unistd.h>
31
32#include "recv.h"
33#include "socket.h"
34#include "utils.h"
35
36static RecvUIFunc recv_ui_func;
37static gpointer recv_ui_func_data;
38
39
40gchar *recv_bytes(SockInfo *sock, glong size)
41{
42 gchar *buf;
43 glong count = 0;
44
45 if (size == 0)
46 return NULL;
47
48 buf = g_malloc(size + 1);
49
50 do {
51 gint read_count;
52
53 read_count = sock_read(sock, buf + count,
54 MIN(BUFFSIZE, size - count));
55 if (read_count <= 0) {
56 g_free(buf);
57 return NULL;
58 }
59 count += read_count;
60 } while (count < size);
61
62 buf[size] = '\0';
63
64 return buf;
65}
66
67gint recv_write_to_file(SockInfo *sock, const gchar *filename)
68{
69 FILE *fp;
70 gint ret;
71
72 g_return_val_if_fail(filename != NULL, -1);
73
74 if ((fp = g_fopen(filename, "wb")) == NULL) {
75 FILE_OP_ERROR(filename, "fopen");
76 recv_write(sock, NULL);
77 return -1;
78 }
79
80 if (change_file_mode_rw(fp, filename) < 0)
81 FILE_OP_ERROR(filename, "chmod");
82
83 if ((ret = recv_write(sock, fp)) < 0) {
84 fclose(fp);
85 g_unlink(filename);
86 return ret;
87 }
88
89 if (fclose(fp) == EOF) {
90 FILE_OP_ERROR(filename, "fclose");
91 g_unlink(filename);
92 return -1;
93 }
94
95 return 0;
96}
97
98gint recv_bytes_write_to_file(SockInfo *sock, glong size, const gchar *filename)
99{
100 FILE *fp;
101 gint ret;
102
103 g_return_val_if_fail(filename != NULL, -1);
104
105 if ((fp = g_fopen(filename, "wb")) == NULL) {
106 FILE_OP_ERROR(filename, "fopen");
107 return recv_bytes_write(sock, size, NULL);
108 }
109
110 if (change_file_mode_rw(fp, filename) < 0)
111 FILE_OP_ERROR(filename, "chmod");
112
113 if ((ret = recv_bytes_write(sock, size, fp)) < 0) {
114 fclose(fp);
115 g_unlink(filename);
116 return ret;
117 }
118
119 if (fclose(fp) == EOF) {
120 FILE_OP_ERROR(filename, "fclose");
121 g_unlink(filename);
122 return -1;
123 }
124
125 return 0;
126}
127
128gint recv_write(SockInfo *sock, FILE *fp)
129{
130 gchar buf[BUFFSIZE];
131 gint len;
132 gint count = 0;
133 gint bytes = 0;
134 gchar *p;
135 GTimeVal tv_prev, tv_cur;
136
137 g_get_current_time(&tv_prev);
138
139 for (;;) {
140 if (sock_gets(sock, buf, sizeof(buf)) < 0) {
141 g_warning(_("error occurred while retrieving data.\n"));
142 return -2;
143 }
144
145 len = strlen(buf);
146 if (len > 1 && buf[0] == '.' && buf[1] == '\r') {
147 if (recv_ui_func)
148 recv_ui_func(sock, count, bytes,
149 recv_ui_func_data);
150 break;
151 }
152 count++;
153 bytes += len;
154
155 if (recv_ui_func) {
156 g_get_current_time(&tv_cur);
157 /* if elapsed time from previous update is greater
158 than 50msec, update UI */
159 if (tv_cur.tv_sec - tv_prev.tv_sec > 0 ||
160 tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) {
161 gboolean ret;
162 ret = recv_ui_func(sock, count, bytes,
163 recv_ui_func_data);
164 if (ret == FALSE) return -1;
165 g_get_current_time(&tv_prev);
166 }
167 }
168
169 p = buf;
170
171 if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
172 buf[len - 2] = '\n';
173 buf[len - 1] = '\0';
174 }
175
176 if (buf[0] == '.' && buf[1] == '.')
177 p++;
178 else if (!strncmp(buf, ">From ", 6))
179 p++;
180
181 if (fp && fputs(p, fp) == EOF) {
182 perror("fputs");
183 g_warning(_("Can't write to file.\n"));
184 fp = NULL;
185 }
186 }
187
188 if (!fp) return -1;
189
190 return 0;
191}
192
193gint recv_bytes_write(SockInfo *sock, glong size, FILE *fp)
194{
195 gchar *buf;
196 gchar *prev, *cur;
197
198 if (size == 0)
199 return 0;
200
201 buf = recv_bytes(sock, size);
202 if (!buf)
203 return -2;
204
205 /* +------------------+----------------+--------------------------+ *
206 * ^buf ^prev ^cur buf+size-1^ */
207
208 prev = buf;
209 while ((cur = memchr(prev, '\r', size - (prev - buf))) != NULL) {
210 if (cur == buf + size - 1) break;
211
212 if (fp && (fwrite(prev, sizeof(gchar), cur - prev, fp) == EOF ||
213 fwrite("\n", sizeof(gchar), 1, fp) == EOF)) {
214 perror("fwrite");
215 g_warning(_("Can't write to file.\n"));
216 fp = NULL;
217 }
218
219 if (*(cur + 1) == '\n')
220 prev = cur + 2;
221 else
222 prev = cur + 1;
223
224 if (prev - buf >= size) break;
225 }
226
227 if (prev - buf < size && fp &&
228 fwrite(prev, sizeof(gchar), size - (prev - buf), fp) == EOF) {
229 perror("fwrite");
230 g_warning(_("Can't write to file.\n"));
231 fp = NULL;
232 }
233
234 g_free(buf);
235
236 if (!fp) return -1;
237
238 return 0;
239}
240
241void recv_set_ui_func(RecvUIFunc func, gpointer data)
242{
243 recv_ui_func = func;
244 recv_ui_func_data = data;
245}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __RECV_H__
21#define __RECV_H__
22
23#include <glib.h>
24
25#include "socket.h"
26
27typedef gboolean (*RecvUIFunc) (SockInfo *sock,
28 gint count,
29 gint read_bytes,
30 gpointer data);
31
32gchar *recv_bytes (SockInfo *sock,
33 glong size);
34
35gint recv_write_to_file (SockInfo *sock,
36 const gchar *filename);
37gint recv_bytes_write_to_file (SockInfo *sock,
38 glong size,
39 const gchar *filename);
40gint recv_write (SockInfo *sock,
41 FILE *fp);
42gint recv_bytes_write (SockInfo *sock,
43 glong size,
44 FILE *fp);
45
46void recv_set_ui_func (RecvUIFunc func,
47 gpointer data);
48
49#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <unistd.h>
32#include <time.h>
33#include <errno.h>
34
35#include "session.h"
36#include "utils.h"
37
38typedef struct _SessionPrivData SessionPrivData;
39
40struct _SessionPrivData {
41 Session *session;
42 SocksInfo *socks_info;
43 SessionErrorValue error_val;
44 gpointer data;
45};
46
47static GList *priv_list = NULL;
48
49static SessionPrivData *session_get_priv(Session *session);
50
51static gint session_connect_cb (SockInfo *sock,
52 gpointer data);
53static gint session_close (Session *session);
54
55static gboolean session_timeout_cb (gpointer data);
56
57#ifdef G_OS_WIN32
58static gboolean session_ping_cb (gpointer data);
59#endif
60
61static gboolean session_recv_msg_idle_cb (gpointer data);
62static gboolean session_recv_data_idle_cb (gpointer data);
63
64static gboolean session_recv_data_as_file_idle_cb (gpointer data);
65
66static gboolean session_read_msg_cb (SockInfo *source,
67 GIOCondition condition,
68 gpointer data);
69static gboolean session_read_data_cb (SockInfo *source,
70 GIOCondition condition,
71 gpointer data);
72
73static gboolean session_read_data_as_file_cb (SockInfo *source,
74 GIOCondition condition,
75 gpointer data);
76
77static gboolean session_write_msg_cb (SockInfo *source,
78 GIOCondition condition,
79 gpointer data);
80static gboolean session_write_data_cb (SockInfo *source,
81 GIOCondition condition,
82 gpointer data);
83
84
85void session_init(Session *session)
86{
87 SessionPrivData *priv;
88
89 session->type = SESSION_UNKNOWN;
90 session->sock = NULL;
91 session->server = NULL;
92 session->port = 0;
93#if USE_SSL
94 session->ssl_type = SSL_NONE;
95#endif
96 session->nonblocking = TRUE;
97 session->state = SESSION_READY;
98 session->last_access_time = time(NULL);
99
100 g_get_current_time(&session->tv_prev);
101
102 session->conn_id = 0;
103
104 session->io_tag = 0;
105
106 session->read_buf_p = session->read_buf;
107 session->read_buf_len = 0;
108
109 session->read_msg_buf = g_string_sized_new(1024);
110 session->read_data_buf = g_byte_array_new();
111 session->read_data_terminator = NULL;
112
113 session->read_data_fp = NULL;
114 session->read_data_pos = 0;
115
116 session->preread_len = 0;
117
118 session->write_buf = NULL;
119 session->write_buf_p = NULL;
120 session->write_buf_len = 0;
121
122 session->write_data_fp = NULL;
123 session->write_data_pos = 0;
124 session->write_data_len = 0;
125
126 session->timeout_tag = 0;
127 session->timeout_interval = 0;
128
129 session->idle_tag = 0;
130 session->ping_tag = 0;
131
132 session->data = NULL;
133
134 priv = g_new0(SessionPrivData, 1);
135 priv->session = session;
136 priv->socks_info = NULL;
137 priv->error_val = SESSION_ERROR_OK;
138 priv_list = g_list_prepend(priv_list, priv);
139}
140
141static SessionPrivData *session_get_priv(Session *session)
142{
143 SessionPrivData *priv;
144 GList *cur;
145
146 g_return_val_if_fail(session != NULL, NULL);
147
148 for (cur = priv_list; cur != NULL; cur = cur->next) {
149 priv = (SessionPrivData *)cur->data;
150 if (priv->session == session)
151 return priv;
152 }
153
154 return NULL;
155}
156
157gint session_connect(Session *session, const gchar *server, gushort port)
158{
159 return session_connect_full(session, server, port, NULL);
160}
161
162gint session_connect_full(Session *session, const gchar *server, gushort port,
163 SocksInfo *socks_info)
164{
165 SessionPrivData *priv;
166#ifndef G_OS_UNIX
167 SockInfo *sock = NULL;
168#endif
169 g_return_val_if_fail(session != NULL, -1);
170 g_return_val_if_fail(server != NULL, -1);
171 g_return_val_if_fail(port > 0, -1);
172
173 priv = session_get_priv(session);
174 g_return_val_if_fail(priv != NULL, -1);
175 priv->socks_info = socks_info;
176
177 if (session->server != server) {
178 g_free(session->server);
179 session->server = g_strdup(server);
180 }
181 session->port = port;
182
183 if (socks_info) {
184 g_return_val_if_fail(socks_info->proxy_host != NULL, -1);
185 server = socks_info->proxy_host;
186 port = socks_info->proxy_port;
187 }
188
189#ifdef G_OS_UNIX
190 session->conn_id = sock_connect_async(server, port, session_connect_cb,
191 session);
192 if (session->conn_id < 0) {
193 g_warning("can't connect to server.");
194 session->state = SESSION_ERROR;
195 priv->error_val = SESSION_ERROR_CONNFAIL;
196 return -1;
197 }
198#elif USE_THREADS
199 session->conn_id = sock_connect_async_thread(server, port);
200 if (session->conn_id < 0) {
201 g_warning("can't connect to server.");
202 session->state = SESSION_ERROR;
203 priv->error_val = SESSION_ERROR_CONNFAIL;
204 return -1;
205 }
206 if (sock_info_connect_async_thread_wait(session->conn_id, &sock) < 0) {
207 session_connect_cb(sock, session);
208 if (sock)
209 sock_close(sock);
210 return -1;
211 }
212#else /* !USE_THREADS */
213 sock = sock_new(server, port);
214 if (sock_info_connect(sock) < 0) {
215 session_connect_cb(sock, session);
216 sock_close(sock);
217 return -1;
218 }
219#endif
220
221#ifdef G_OS_UNIX
222 return 0;
223#else
224 return session_connect_cb(sock, session);
225#endif
226}
227
228static gint session_connect_cb(SockInfo *sock, gpointer data)
229{
230 Session *session = SESSION(data);
231 SessionPrivData *priv;
232
233 priv = session_get_priv(session);
234 session->conn_id = 0;
235
236 if (!sock) {
237 g_warning("can't connect to server.");
238 session->state = SESSION_ERROR;
239 priv->error_val = SESSION_ERROR_CONNFAIL;
240 return -1;
241 }
242 if (sock->state == CONN_LOOKUPFAILED) {
243 g_warning("DNS lookup failed.");
244 session->state = SESSION_ERROR;
245 priv->error_val = SESSION_ERROR_LOOKUP;
246 return -1;
247 } else if (sock->state != CONN_ESTABLISHED) {
248 g_warning("can't connect to server (ConnectionState: %d).",
249 sock->state);
250 session->state = SESSION_ERROR;
251 priv->error_val = SESSION_ERROR_CONNFAIL;
252 return -1;
253 }
254
255 session->sock = sock;
256
257 if (priv->socks_info) {
258 sock_set_nonblocking_mode(sock, FALSE);
259 if (socks_connect(sock, session->server, session->port,
260 priv->socks_info) < 0) {
261 g_warning("can't establish SOCKS connection.");
262 session->state = SESSION_ERROR;
263 priv->error_val = SESSION_ERROR_CONNFAIL;
264 return -1;
265 }
266 }
267
268#if USE_SSL
269 if (session->ssl_type == SSL_TUNNEL) {
270 sock_set_nonblocking_mode(sock, FALSE);
271 if (!ssl_init_socket(sock)) {
272 g_warning("can't initialize SSL.");
273 session->state = SESSION_ERROR;
274 priv->error_val = SESSION_ERROR_SOCKET;
275 return -1;
276 }
277 }
278#endif
279
280 debug_print("session (%p): connected\n", session);
281
282 sock_set_nonblocking_mode(sock, session->nonblocking);
283
284 session->state = SESSION_RECV;
285 priv->error_val = SESSION_ERROR_OK;
286 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
287 session_read_msg_cb,
288 session);
289
290#ifdef G_OS_WIN32
291 session->ping_tag = g_timeout_add_full(G_PRIORITY_LOW, 1000, session_ping_cb, session, NULL);
292#endif
293
294 return 0;
295}
296
297gint session_disconnect(Session *session)
298{
299 session_close(session);
300 return 0;
301}
302
303void session_destroy(Session *session)
304{
305 SessionPrivData *priv;
306
307 g_return_if_fail(session != NULL);
308 g_return_if_fail(session->destroy != NULL);
309
310 session_close(session);
311 session->destroy(session);
312 g_free(session->server);
313 g_string_free(session->read_msg_buf, TRUE);
314 g_byte_array_free(session->read_data_buf, TRUE);
315 g_free(session->read_data_terminator);
316 if (session->read_data_fp)
317 fclose(session->read_data_fp);
318 g_free(session->write_buf);
319
320 priv = session_get_priv(session);
321 if (priv) {
322 priv_list = g_list_remove(priv_list, priv);
323 socks_info_free(priv->socks_info);
324 g_free(priv);
325 }
326
327 debug_print("session (%p): destroyed\n", session);
328
329 g_free(session);
330}
331
332gboolean session_is_connected(Session *session)
333{
334 return (session->state == SESSION_READY ||
335 session->state == SESSION_SEND ||
336 session->state == SESSION_RECV);
337}
338
339SessionErrorValue session_get_error(Session *session)
340{
341 SessionPrivData *priv;
342
343 priv = session_get_priv(session);
344 if (priv)
345 return priv->error_val;
346 else
347 return SESSION_ERROR_ERROR;
348}
349
350void session_set_access_time(Session *session)
351{
352 session->last_access_time = time(NULL);
353}
354
355void session_set_timeout(Session *session, guint interval)
356{
357 if (session->timeout_tag > 0)
358 g_source_remove(session->timeout_tag);
359
360 session->timeout_interval = interval;
361 if (interval > 0)
362 session->timeout_tag =
363 g_timeout_add_full(G_PRIORITY_LOW, interval, session_timeout_cb, session, NULL);
364 else
365 session->timeout_tag = 0;
366}
367
368static gboolean session_timeout_cb(gpointer data)
369{
370 Session *session = SESSION(data);
371 SessionPrivData *priv;
372
373 g_warning("session timeout.\n");
374
375 if (session->io_tag > 0) {
376 g_source_remove(session->io_tag);
377 session->io_tag = 0;
378 }
379
380 session->timeout_tag = 0;
381 session->state = SESSION_TIMEOUT;
382 priv = session_get_priv(session);
383 priv->error_val = SESSION_ERROR_TIMEOUT;
384
385 return FALSE;
386}
387
388#ifdef G_OS_WIN32
389/* hack for state machine freeze problem in GLib >= 2.8.x */
390static gboolean session_ping_cb(gpointer data)
391{
392 Session *session = SESSION(data);
393 SockInfo *sock = session->sock;
394
395 if (!session_is_connected(session))
396 return FALSE;
397
398 if (session->io_tag > 0 && sock && sock->callback) {
399 GTimeVal tv_cur, tv_result;
400
401 g_get_current_time(&tv_cur);
402 tv_result.tv_sec = tv_cur.tv_sec - session->tv_prev.tv_sec;
403 tv_result.tv_usec = tv_cur.tv_usec - session->tv_prev.tv_usec;
404 if (tv_result.tv_usec < 0) {
405 tv_result.tv_sec--;
406 tv_result.tv_usec += G_USEC_PER_SEC;
407 }
408 if (tv_result.tv_sec * G_USEC_PER_SEC + tv_result.tv_usec >
409 G_USEC_PER_SEC) {
410 SockFlags save_flags;
411
412 debug_print("state machine freeze for 1 second detected, forcing dispatch.\n");
413 save_flags = sock->flags;
414 SOCK_UNSET_FLAGS(sock->flags, SYL_SOCK_CHECK_IO);
415 sock->callback(sock, sock->condition, sock->data);
416 sock->flags = save_flags;
417 }
418 }
419
420 return TRUE;
421}
422#endif
423
424void session_set_recv_message_notify(Session *session,
425 RecvMsgNotify notify_func, gpointer data)
426{
427 session->recv_msg_notify = notify_func;
428 session->recv_msg_notify_data = data;
429}
430
431void session_set_recv_data_progressive_notify
432 (Session *session,
433 RecvDataProgressiveNotify notify_func,
434 gpointer data)
435{
436 session->recv_data_progressive_notify = notify_func,
437 session->recv_data_progressive_notify_data = data;
438}
439
440void session_set_recv_data_notify(Session *session, RecvDataNotify notify_func,
441 gpointer data)
442{
443 session->recv_data_notify = notify_func;
444 session->recv_data_notify_data = data;
445}
446
447void session_set_send_data_progressive_notify
448 (Session *session,
449 SendDataProgressiveNotify notify_func,
450 gpointer data)
451{
452 session->send_data_progressive_notify = notify_func;
453 session->send_data_progressive_notify_data = data;
454}
455
456void session_set_send_data_notify(Session *session, SendDataNotify notify_func,
457 gpointer data)
458{
459 session->send_data_notify = notify_func;
460 session->send_data_notify_data = data;
461}
462
463static gint session_close(Session *session)
464{
465 g_return_val_if_fail(session != NULL, -1);
466
467#ifdef G_OS_UNIX
468 if (session->conn_id > 0) {
469 sock_connect_async_cancel(session->conn_id);
470 session->conn_id = 0;
471 debug_print("session (%p): connection cancelled\n", session);
472 }
473#endif
474
475 session_set_timeout(session, 0);
476
477 if (session->idle_tag > 0) {
478 g_source_remove(session->idle_tag);
479 session->idle_tag = 0;
480 }
481
482#ifdef G_OS_WIN32
483 if (session->ping_tag > 0) {
484 g_source_remove(session->ping_tag);
485 session->ping_tag = 0;
486 }
487#endif
488
489 if (session->io_tag > 0) {
490 g_source_remove(session->io_tag);
491 session->io_tag = 0;
492 }
493
494 if (session->sock) {
495 sock_close(session->sock);
496 session->sock = NULL;
497 session->state = SESSION_DISCONNECTED;
498 debug_print("session (%p): closed\n", session);
499 }
500
501 return 0;
502}
503
504#if USE_SSL
505gint session_start_tls(Session *session)
506{
507 gboolean nb_mode;
508
509 nb_mode = sock_is_nonblocking_mode(session->sock);
510
511 sock_set_nonblocking_mode(session->sock, FALSE);
512
513 if (!ssl_init_socket_with_method(session->sock, SSL_METHOD_TLSv1)) {
514 g_warning("can't start TLS session.\n");
515 if (nb_mode)
516 sock_set_nonblocking_mode(session->sock, TRUE);
517 return -1;
518 }
519
520 sock_set_nonblocking_mode(session->sock, session->nonblocking);
521
522 return 0;
523}
524#endif
525
526gint session_send_msg(Session *session, SessionMsgType type, const gchar *msg)
527{
528 gboolean ret;
529
530 g_return_val_if_fail(session->sock != NULL, -1);
531 g_return_val_if_fail(session->write_buf == NULL, -1);
532 g_return_val_if_fail(msg != NULL, -1);
533 g_return_val_if_fail(msg[0] != '\0', -1);
534
535 session->state = SESSION_SEND;
536 session->write_buf = g_strconcat(msg, "\r\n", NULL);
537 session->write_buf_p = session->write_buf;
538 session->write_buf_len = strlen(msg) + 2;
539
540 ret = session_write_msg_cb(session->sock, G_IO_OUT, session);
541
542 if (ret == TRUE)
543 session->io_tag = sock_add_watch(session->sock, G_IO_OUT,
544 session_write_msg_cb, session);
545 else if (session->state == SESSION_ERROR)
546 return -1;
547
548 return 0;
549}
550
551gint session_recv_msg(Session *session)
552{
553 g_return_val_if_fail(session->sock != NULL, -1);
554 g_return_val_if_fail(session->read_msg_buf->len == 0, -1);
555
556 session->state = SESSION_RECV;
557
558 if (session->read_buf_len > 0)
559 session->idle_tag = g_idle_add(session_recv_msg_idle_cb,
560 session);
561 else
562 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
563 session_read_msg_cb, session);
564
565 return 0;
566}
567
568static gboolean session_recv_msg_idle_cb(gpointer data)
569{
570 Session *session = SESSION(data);
571 gboolean ret;
572
573#if GLIB_CHECK_VERSION(2, 12, 0)
574 if (g_source_is_destroyed(g_main_current_source()))
575 return FALSE;
576#endif
577
578 session->idle_tag = 0;
579 ret = session_read_msg_cb(session->sock, G_IO_IN, session);
580
581 if (ret == TRUE)
582 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
583 session_read_msg_cb, session);
584
585 return FALSE;
586}
587
588gint session_send_data(Session *session, FILE *data_fp, guint size)
589{
590 gboolean ret;
591
592 g_return_val_if_fail(session->sock != NULL, -1);
593 g_return_val_if_fail(session->write_data_fp == NULL, -1);
594 g_return_val_if_fail(data_fp != NULL, -1);
595 g_return_val_if_fail(size != 0, -1);
596
597 session->state = SESSION_SEND;
598
599 session->write_data_fp = data_fp;
600 session->write_data_pos = 0;
601 session->write_data_len = size;
602 g_get_current_time(&session->tv_prev);
603
604#ifdef G_OS_WIN32
605 sock_set_nonblocking_mode(session->sock, FALSE);
606#endif
607
608 ret = session_write_data_cb(session->sock, G_IO_OUT, session);
609
610 if (ret == TRUE)
611#ifdef G_OS_WIN32
612 session->io_tag = sock_add_watch_poll(session->sock, G_IO_OUT,
613 session_write_data_cb,
614 session);
615#else
616 session->io_tag = sock_add_watch(session->sock, G_IO_OUT,
617 session_write_data_cb,
618 session);
619#endif
620 else if (session->state == SESSION_ERROR)
621 return -1;
622
623 return 0;
624}
625
626gint session_recv_data(Session *session, guint size, const gchar *terminator)
627{
628 g_return_val_if_fail(session->sock != NULL, -1);
629 g_return_val_if_fail(session->read_data_buf->len == 0, -1);
630
631 session->state = SESSION_RECV;
632
633 g_free(session->read_data_terminator);
634 session->read_data_terminator = g_strdup(terminator);
635 g_get_current_time(&session->tv_prev);
636
637 if (session->read_buf_len > 0)
638 session->idle_tag = g_idle_add(session_recv_data_idle_cb,
639 session);
640 else
641 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
642 session_read_data_cb, session);
643
644 return 0;
645}
646
647static gboolean session_recv_data_idle_cb(gpointer data)
648{
649 Session *session = SESSION(data);
650 gboolean ret;
651
652#if GLIB_CHECK_VERSION(2, 12, 0)
653 if (g_source_is_destroyed(g_main_current_source()))
654 return FALSE;
655#endif
656
657 session->idle_tag = 0;
658 ret = session_read_data_cb(session->sock, G_IO_IN, session);
659
660 if (ret == TRUE)
661 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
662 session_read_data_cb, session);
663
664 return FALSE;
665}
666
667gint session_recv_data_as_file(Session *session, guint size,
668 const gchar *terminator)
669{
670 g_return_val_if_fail(session->sock != NULL, -1);
671 g_return_val_if_fail(session->read_data_pos == 0, -1);
672 g_return_val_if_fail(session->read_data_fp == NULL, -1);
673
674 session->state = SESSION_RECV;
675
676 g_free(session->read_data_terminator);
677 session->read_data_terminator = g_strdup(terminator);
678 g_get_current_time(&session->tv_prev);
679
680 session->read_data_fp = my_tmpfile();
681 if (!session->read_data_fp) {
682 FILE_OP_ERROR("session_recv_data_as_file", "my_tmpfile");
683 return -1;
684 }
685
686 if (session->read_buf_len > 0)
687 session->idle_tag =
688 g_idle_add(session_recv_data_as_file_idle_cb, session);
689 else
690 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
691 session_read_data_as_file_cb,
692 session);
693
694 return 0;
695}
696
697static gboolean session_recv_data_as_file_idle_cb(gpointer data)
698{
699 Session *session = SESSION(data);
700 gboolean ret;
701
702#if GLIB_CHECK_VERSION(2, 12, 0)
703 if (g_source_is_destroyed(g_main_current_source()))
704 return FALSE;
705#endif
706
707 session->idle_tag = 0;
708 ret = session_read_data_as_file_cb(session->sock, G_IO_IN, session);
709
710 if (ret == TRUE)
711 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
712 session_read_data_as_file_cb,
713 session);
714
715 return FALSE;
716}
717
718static gboolean session_read_msg_cb(SockInfo *source, GIOCondition condition,
719 gpointer data)
720{
721 Session *session = SESSION(data);
722 SessionPrivData *priv;
723 gchar buf[SESSION_BUFFSIZE];
724 gint line_len;
725 gchar *newline;
726 gchar *msg;
727 gint ret;
728
729 g_return_val_if_fail(condition == G_IO_IN, FALSE);
730
731 if (session->read_buf_len == 0) {
732 gint read_len;
733
734 read_len = sock_read(session->sock, session->read_buf,
735 SESSION_BUFFSIZE - 1);
736
737 if (read_len == 0) {
738 g_warning("sock_read: received EOF\n");
739 session->state = SESSION_EOF;
740 return FALSE;
741 }
742
743 if (read_len < 0) {
744 switch (errno) {
745 case EAGAIN:
746 return TRUE;
747 default:
748 g_warning("%s: sock_read: %s\n", G_STRFUNC, g_strerror(errno));
749 session->state = SESSION_ERROR;
750 priv = session_get_priv(session);
751 priv->error_val = SESSION_ERROR_SOCKET;
752 return FALSE;
753 }
754 }
755
756 session->read_buf_len = read_len;
757 }
758
759 session_set_timeout(session, session->timeout_interval);
760
761 if ((newline = memchr(session->read_buf_p, '\n', session->read_buf_len))
762 != NULL)
763 line_len = newline - session->read_buf_p + 1;
764 else
765 line_len = session->read_buf_len;
766
767 if (line_len == 0)
768 return TRUE;
769
770 memcpy(buf, session->read_buf_p, line_len);
771 buf[line_len] = '\0';
772
773 g_string_append(session->read_msg_buf, buf);
774
775 session->read_buf_len -= line_len;
776 if (session->read_buf_len == 0)
777 session->read_buf_p = session->read_buf;
778 else
779 session->read_buf_p += line_len;
780
781 /* incomplete read */
782 if (buf[line_len - 1] != '\n')
783 return TRUE;
784
785 /* complete */
786 if (session->io_tag > 0) {
787 g_source_remove(session->io_tag);
788 session->io_tag = 0;
789 }
790
791 /* callback */
792 msg = g_strdup(session->read_msg_buf->str);
793 strretchomp(msg);
794 g_string_truncate(session->read_msg_buf, 0);
795
796 ret = session->recv_msg(session, msg);
797 if (session->recv_msg_notify)
798 session->recv_msg_notify(session, msg,
799 session->recv_msg_notify_data);
800
801 g_free(msg);
802
803 if (ret < 0) {
804 session->state = SESSION_ERROR;
805 priv = session_get_priv(session);
806 priv->error_val = SESSION_ERROR_SOCKET;
807 }
808
809 return FALSE;
810}
811
812static gboolean session_read_data_cb(SockInfo *source, GIOCondition condition,
813 gpointer data)
814{
815 Session *session = SESSION(data);
816 SessionPrivData *priv;
817 GByteArray *data_buf;
818 gint terminator_len;
819 gboolean complete = FALSE;
820 guint data_len;
821 gint ret;
822
823 g_return_val_if_fail(condition == G_IO_IN, FALSE);
824
825 if (session->read_buf_len == 0) {
826 gint read_len;
827
828 read_len = sock_read(session->sock, session->read_buf,
829 SESSION_BUFFSIZE);
830
831 if (read_len == 0) {
832 g_warning("sock_read: received EOF\n");
833 session->state = SESSION_EOF;
834 return FALSE;
835 }
836
837 if (read_len < 0) {
838 switch (errno) {
839 case EAGAIN:
840 return TRUE;
841 default:
842 g_warning("%s: sock_read: %s\n", G_STRFUNC, g_strerror(errno));
843 session->state = SESSION_ERROR;
844 priv = session_get_priv(session);
845 priv->error_val = SESSION_ERROR_SOCKET;
846 return FALSE;
847 }
848 }
849
850 session->read_buf_len = read_len;
851 }
852
853 session_set_timeout(session, session->timeout_interval);
854
855 data_buf = session->read_data_buf;
856 terminator_len = strlen(session->read_data_terminator);
857
858 if (session->read_buf_len == 0)
859 return TRUE;
860
861 g_byte_array_append(data_buf, (guchar *)session->read_buf_p,
862 session->read_buf_len);
863
864 session->read_buf_len = 0;
865 session->read_buf_p = session->read_buf;
866
867 /* check if data is terminated */
868 if (data_buf->len >= terminator_len) {
869 if (memcmp(data_buf->data, session->read_data_terminator,
870 terminator_len) == 0)
871 complete = TRUE;
872 else if (data_buf->len >= terminator_len + 2 &&
873 memcmp(data_buf->data + data_buf->len -
874 (terminator_len + 2), "\r\n", 2) == 0 &&
875 memcmp(data_buf->data + data_buf->len -
876 terminator_len, session->read_data_terminator,
877 terminator_len) == 0)
878 complete = TRUE;
879 }
880
881 /* incomplete read */
882 if (!complete) {
883 GTimeVal tv_cur;
884
885 g_get_current_time(&tv_cur);
886 if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 ||
887 tv_cur.tv_usec - session->tv_prev.tv_usec >
888 UI_REFRESH_INTERVAL) {
889 if (session->recv_data_progressive_notify)
890 session->recv_data_progressive_notify
891 (session, data_buf->len, 0,
892 session->recv_data_progressive_notify_data);
893 g_get_current_time(&session->tv_prev);
894 }
895 return TRUE;
896 }
897
898 /* complete */
899 if (session->io_tag > 0) {
900 g_source_remove(session->io_tag);
901 session->io_tag = 0;
902 }
903
904 data_len = data_buf->len - terminator_len;
905
906 /* callback */
907 ret = session->recv_data_finished(session, (guchar *)data_buf->data,
908 data_len);
909
910 g_byte_array_set_size(data_buf, 0);
911
912 if (session->recv_data_notify)
913 session->recv_data_notify(session, data_len,
914 session->recv_data_notify_data);
915
916 if (ret < 0) {
917 session->state = SESSION_ERROR;
918 priv = session_get_priv(session);
919 priv->error_val = SESSION_ERROR_SOCKET;
920 }
921
922 return FALSE;
923}
924
925#define READ_BUF_LEFT() \
926 (SESSION_BUFFSIZE - (session->read_buf_p - session->read_buf) - \
927 session->read_buf_len)
928#define PREREAD_SIZE 8
929
930static gboolean session_read_data_as_file_cb(SockInfo *source,
931 GIOCondition condition,
932 gpointer data)
933{
934 Session *session = SESSION(data);
935 SessionPrivData *priv;
936 gint terminator_len;
937 gchar *data_begin_p;
938 gint buf_data_len;
939 gboolean complete = FALSE;
940 gint read_len;
941 gint write_len;
942 gint ret;
943
944 g_return_val_if_fail(condition == G_IO_IN, FALSE);
945
946 if (session->read_buf_len == 0) {
947 read_len = sock_read(session->sock, session->read_buf_p,
948 READ_BUF_LEFT());
949
950 if (read_len == 0) {
951 g_warning("sock_read: received EOF\n");
952 session->state = SESSION_EOF;
953 return FALSE;
954 }
955
956 if (read_len < 0) {
957 switch (errno) {
958 case EAGAIN:
959 return TRUE;
960 default:
961 g_warning("%s: sock_read: %s\n", G_STRFUNC, g_strerror(errno));
962 session->state = SESSION_ERROR;
963 priv = session_get_priv(session);
964 priv->error_val = SESSION_ERROR_SOCKET;
965 return FALSE;
966 }
967 }
968
969 session->read_buf_len = read_len;
970 }
971
972 session_set_timeout(session, session->timeout_interval);
973
974 terminator_len = strlen(session->read_data_terminator);
975
976 if (session->read_buf_len == 0)
977 return TRUE;
978
979 /* +---------------buf_data_len---------------+
980 * +--preread_len--+-------read_buf_len-------+
981 * +---------------+--------------------------+-------------------+ *
982 * ^data_begin_p ^read_buf_p
983 * ^read_buf
984 */
985
986 data_begin_p = session->read_buf_p - session->preread_len;
987 buf_data_len = session->preread_len + session->read_buf_len;
988
989 /* check if data is terminated */
990 if (buf_data_len >= terminator_len) {
991 if (session->read_data_pos == 0 &&
992 buf_data_len == terminator_len &&
993 memcmp(data_begin_p, session->read_data_terminator,
994 terminator_len) == 0)
995 complete = TRUE;
996 else if (buf_data_len >= terminator_len + 2 &&
997 memcmp(data_begin_p + buf_data_len -
998 (terminator_len + 2), "\r\n", 2) == 0 &&
999 memcmp(data_begin_p + buf_data_len -
1000 terminator_len, session->read_data_terminator,
1001 terminator_len) == 0)
1002 complete = TRUE;
1003 }
1004
1005 /* incomplete read */
1006 if (!complete) {
1007 GTimeVal tv_cur;
1008
1009 if (buf_data_len <= PREREAD_SIZE) {
1010 if (data_begin_p > session->read_buf) {
1011 g_memmove(session->read_buf, data_begin_p,
1012 buf_data_len);
1013 data_begin_p = session->read_buf;
1014 session->read_buf_p = session->read_buf +
1015 session->preread_len;
1016 }
1017 session->read_buf_p += session->read_buf_len;
1018 session->preread_len = buf_data_len;
1019 session->read_buf_len = 0;
1020 return TRUE;
1021 }
1022
1023 if (READ_BUF_LEFT() >= (SESSION_BUFFSIZE / 2)) {
1024 session->read_buf_p += session->read_buf_len;
1025 session->preread_len = buf_data_len;
1026 session->read_buf_len = 0;
1027 return TRUE;
1028 }
1029
1030 write_len = buf_data_len - PREREAD_SIZE;
1031 if (fwrite(data_begin_p, write_len, 1,
1032 session->read_data_fp) < 1) {
1033 g_warning("session_read_data_as_file_cb: "
1034 "writing data to file failed\n");
1035 session->state = SESSION_ERROR;
1036 priv = session_get_priv(session);
1037 priv->error_val = SESSION_ERROR_IO;
1038 return FALSE;
1039 }
1040 session->read_data_pos += write_len;
1041
1042 g_memmove(session->read_buf, data_begin_p + write_len,
1043 PREREAD_SIZE);
1044 session->read_buf_p = session->read_buf + PREREAD_SIZE;
1045 session->preread_len = PREREAD_SIZE;
1046 session->read_buf_len = 0;
1047
1048 g_get_current_time(&tv_cur);
1049 if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 ||
1050 tv_cur.tv_usec - session->tv_prev.tv_usec >
1051 UI_REFRESH_INTERVAL) {
1052 if (session->recv_data_progressive_notify)
1053 session->recv_data_progressive_notify
1054 (session, session->read_data_pos, 0,
1055 session->recv_data_progressive_notify_data);
1056 g_get_current_time(&session->tv_prev);
1057 }
1058
1059 return TRUE;
1060 }
1061
1062 /* complete */
1063 if (session->io_tag > 0) {
1064 g_source_remove(session->io_tag);
1065 session->io_tag = 0;
1066 }
1067
1068 write_len = buf_data_len - terminator_len;
1069 if (write_len > 0 && fwrite(data_begin_p, write_len, 1,
1070 session->read_data_fp) < 1) {
1071 g_warning("session_read_data_as_file_cb: "
1072 "writing data to file failed\n");
1073 session->state = SESSION_ERROR;
1074 priv = session_get_priv(session);
1075 priv->error_val = SESSION_ERROR_IO;
1076 return FALSE;
1077 }
1078 session->read_data_pos += write_len;
1079
1080 if (fflush(session->read_data_fp) == EOF) {
1081 perror("fflush");
1082 g_warning("session_read_data_as_file_cb: "
1083 "writing data to file failed\n");
1084 session->state = SESSION_ERROR;
1085 priv = session_get_priv(session);
1086 priv->error_val = SESSION_ERROR_IO;
1087 return FALSE;
1088 }
1089 rewind(session->read_data_fp);
1090
1091 session->preread_len = 0;
1092 session->read_buf_len = 0;
1093 session->read_buf_p = session->read_buf;
1094
1095 /* callback */
1096 ret = session->recv_data_as_file_finished
1097 (session, session->read_data_fp, session->read_data_pos);
1098
1099 fclose(session->read_data_fp);
1100 session->read_data_fp = NULL;
1101
1102 if (session->recv_data_notify)
1103 session->recv_data_notify(session, session->read_data_pos,
1104 session->recv_data_notify_data);
1105
1106 session->read_data_pos = 0;
1107
1108 if (ret < 0) {
1109 session->state = SESSION_ERROR;
1110 priv = session_get_priv(session);
1111 priv->error_val = SESSION_ERROR_IO;
1112 }
1113
1114 return FALSE;
1115}
1116
1117static gint session_write_buf(Session *session)
1118{
1119 gint write_len;
1120 gint to_write_len;
1121 SessionPrivData *priv;
1122
1123 g_return_val_if_fail(session->write_buf != NULL, -1);
1124 g_return_val_if_fail(session->write_buf_p != NULL, -1);
1125 g_return_val_if_fail(session->write_buf_len > 0, -1);
1126
1127 to_write_len = session->write_buf_len -
1128 (session->write_buf_p - session->write_buf);
1129 to_write_len = MIN(to_write_len, SESSION_BUFFSIZE);
1130
1131 write_len = sock_write(session->sock, session->write_buf_p,
1132 to_write_len);
1133
1134 if (write_len < 0) {
1135 switch (errno) {
1136 case EAGAIN:
1137 write_len = 0;
1138 break;
1139 default:
1140 g_warning("sock_write: %s\n", g_strerror(errno));
1141 session->state = SESSION_ERROR;
1142 priv = session_get_priv(session);
1143 priv->error_val = SESSION_ERROR_SOCKET;
1144 return -1;
1145 }
1146 }
1147
1148 /* incomplete write */
1149 if (session->write_buf_p - session->write_buf + write_len <
1150 session->write_buf_len) {
1151 session->write_buf_p += write_len;
1152 return 1;
1153 }
1154
1155 g_free(session->write_buf);
1156 session->write_buf = NULL;
1157 session->write_buf_p = NULL;
1158 session->write_buf_len = 0;
1159
1160 return 0;
1161}
1162
1163#define WRITE_DATA_BUFFSIZE 8192
1164
1165static gint session_write_data(Session *session, gint *nwritten)
1166{
1167 gchar buf[WRITE_DATA_BUFFSIZE];
1168 gint write_len;
1169 gint to_write_len;
1170 SessionPrivData *priv;
1171
1172 g_return_val_if_fail(session->write_data_fp != NULL, -1);
1173 g_return_val_if_fail(session->write_data_pos >= 0, -1);
1174 g_return_val_if_fail(session->write_data_len > 0, -1);
1175
1176 to_write_len = session->write_data_len - session->write_data_pos;
1177 to_write_len = MIN(to_write_len, WRITE_DATA_BUFFSIZE);
1178 if (fread(buf, to_write_len, 1, session->write_data_fp) < 1) {
1179 g_warning("session_write_data: reading data from file failed\n");
1180 session->state = SESSION_ERROR;
1181 priv = session_get_priv(session);
1182 priv->error_val = SESSION_ERROR_IO;
1183 return -1;
1184 }
1185
1186 write_len = sock_write(session->sock, buf, to_write_len);
1187
1188 if (write_len < 0) {
1189 switch (errno) {
1190 case EAGAIN:
1191 write_len = 0;
1192 break;
1193 default:
1194 g_warning("sock_write: %s\n", g_strerror(errno));
1195 session->state = SESSION_ERROR;
1196 priv = session_get_priv(session);
1197 priv->error_val = SESSION_ERROR_SOCKET;
1198 *nwritten = write_len;
1199 return -1;
1200 }
1201 }
1202
1203 *nwritten = write_len;
1204
1205 /* incomplete write */
1206 if (session->write_data_pos + write_len < session->write_data_len) {
1207 session->write_data_pos += write_len;
1208 if (write_len < to_write_len) {
1209 if (fseek(session->write_data_fp,
1210 session->write_data_pos, SEEK_SET) < 0) {
1211 g_warning("session_write_data: file seek failed\n");
1212 session->state = SESSION_ERROR;
1213 priv = session_get_priv(session);
1214 priv->error_val = SESSION_ERROR_IO;
1215 return -1;
1216 }
1217 }
1218 return 1;
1219 }
1220
1221 session->write_data_fp = NULL;
1222 session->write_data_pos = 0;
1223 session->write_data_len = 0;
1224
1225 return 0;
1226}
1227
1228static gboolean session_write_msg_cb(SockInfo *source, GIOCondition condition,
1229 gpointer data)
1230{
1231 Session *session = SESSION(data);
1232 SessionPrivData *priv;
1233 gint ret;
1234
1235 g_return_val_if_fail(condition == G_IO_OUT, FALSE);
1236 g_return_val_if_fail(session->write_buf != NULL, FALSE);
1237 g_return_val_if_fail(session->write_buf_p != NULL, FALSE);
1238 g_return_val_if_fail(session->write_buf_len > 0, FALSE);
1239
1240 ret = session_write_buf(session);
1241
1242 if (ret < 0) {
1243 session->state = SESSION_ERROR;
1244 priv = session_get_priv(session);
1245 if (priv->error_val == SESSION_ERROR_OK)
1246 priv->error_val = SESSION_ERROR_IO;
1247 return FALSE;
1248 } else if (ret > 0)
1249 return TRUE;
1250
1251 if (session->io_tag > 0) {
1252 g_source_remove(session->io_tag);
1253 session->io_tag = 0;
1254 }
1255
1256 session_recv_msg(session);
1257
1258 return FALSE;
1259}
1260
1261static gboolean session_write_data_cb(SockInfo *source,
1262 GIOCondition condition, gpointer data)
1263{
1264 Session *session = SESSION(data);
1265 SessionPrivData *priv;
1266 guint write_data_len;
1267 gint write_len;
1268 gint ret;
1269
1270 g_return_val_if_fail(condition == G_IO_OUT, FALSE);
1271 g_return_val_if_fail(session->write_data_fp != NULL, FALSE);
1272 g_return_val_if_fail(session->write_data_pos >= 0, FALSE);
1273 g_return_val_if_fail(session->write_data_len > 0, FALSE);
1274
1275 write_data_len = session->write_data_len;
1276
1277 ret = session_write_data(session, &write_len);
1278
1279 if (ret < 0) {
1280 session->state = SESSION_ERROR;
1281 priv = session_get_priv(session);
1282 if (priv->error_val == SESSION_ERROR_OK)
1283 priv->error_val = SESSION_ERROR_IO;
1284 return FALSE;
1285 } else if (ret > 0) {
1286 GTimeVal tv_cur;
1287
1288 g_get_current_time(&tv_cur);
1289 if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 ||
1290 tv_cur.tv_usec - session->tv_prev.tv_usec >
1291 UI_REFRESH_INTERVAL) {
1292 session_set_timeout(session, session->timeout_interval);
1293 if (session->send_data_progressive_notify)
1294 session->send_data_progressive_notify
1295 (session,
1296 session->write_data_pos,
1297 write_data_len,
1298 session->send_data_progressive_notify_data);
1299 g_get_current_time(&session->tv_prev);
1300 }
1301 return TRUE;
1302 }
1303
1304 if (session->io_tag > 0) {
1305 g_source_remove(session->io_tag);
1306 session->io_tag = 0;
1307 }
1308
1309 /* callback */
1310 ret = session->send_data_finished(session, write_data_len);
1311 if (session->send_data_notify)
1312 session->send_data_notify(session, write_data_len,
1313 session->send_data_notify_data);
1314
1315#ifdef G_OS_WIN32
1316 sock_set_nonblocking_mode(session->sock, session->nonblocking);
1317#endif
1318
1319 return FALSE;
1320}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __SESSION_H__
21#define __SESSION_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28#include <stdio.h>
29#include <time.h>
30#include <unistd.h>
31
32#include "socket.h"
33#include "socks.h"
34#include "utils.h"
35
36#define SESSION_BUFFSIZE 8192
37
38typedef struct _Session Session;
39
40#define SESSION(obj) ((Session *)obj)
41
42typedef enum {
43 SESSION_UNKNOWN,
44 SESSION_IMAP,
45 SESSION_NEWS,
46 SESSION_SMTP,
47 SESSION_POP3
48} SessionType;
49
50typedef enum {
51 SESSION_READY,
52 SESSION_SEND,
53 SESSION_RECV,
54 SESSION_EOF,
55 SESSION_TIMEOUT,
56 SESSION_ERROR,
57 SESSION_DISCONNECTED
58} SessionState;
59
60typedef enum
61{
62 SESSION_MSG_NORMAL,
63 SESSION_MSG_SEND_DATA,
64 SESSION_MSG_RECV_DATA,
65 SESSION_MSG_CONTROL,
66 SESSION_MSG_ERROR,
67 SESSION_MSG_UNKNOWN
68} SessionMsgType;
69
70#ifndef USE_SSL
71typedef enum
72{
73 SSL_NONE
74} SSLType;
75#endif
76
77typedef enum {
78 SESSION_ERROR_OK,
79 SESSION_ERROR_LOOKUP,
80 SESSION_ERROR_CONNFAIL,
81 SESSION_ERROR_IO,
82 SESSION_ERROR_SOCKET,
83 SESSION_ERROR_TIMEOUT,
84 SESSION_ERROR_ERROR
85} SessionErrorValue;
86
87typedef gint (*RecvMsgNotify) (Session *session,
88 const gchar *msg,
89 gpointer user_data);
90typedef gint (*RecvDataProgressiveNotify) (Session *session,
91 guint cur_len,
92 guint total_len,
93 gpointer user_data);
94typedef gint (*RecvDataNotify) (Session *session,
95 guint len,
96 gpointer user_data);
97typedef gint (*SendDataProgressiveNotify) (Session *session,
98 guint cur_len,
99 guint total_len,
100 gpointer user_data);
101typedef gint (*SendDataNotify) (Session *session,
102 guint len,
103 gpointer user_data);
104
105struct _Session
106{
107 SessionType type;
108
109 SockInfo *sock;
110
111 gchar *server;
112 gushort port;
113
114 SSLType ssl_type;
115
116 gboolean nonblocking;
117
118 SessionState state;
119
120 stime_t last_access_time;
121 GTimeVal tv_prev;
122
123 gint conn_id;
124
125 gint io_tag;
126
127 gchar read_buf[SESSION_BUFFSIZE];
128 gchar *read_buf_p;
129 gint read_buf_len;
130
131 /* buffer for short messages */
132 GString *read_msg_buf;
133
134 /* buffer for relatively short multiple lines data */
135 GByteArray *read_data_buf;
136 gchar *read_data_terminator;
137
138 /* buffer for large data */
139 FILE *read_data_fp;
140 gint read_data_pos;
141
142 gint preread_len;
143
144 /* buffer for short messages */
145 gchar *write_buf;
146 gchar *write_buf_p;
147 gint write_buf_len;
148
149 /* buffer for large data */
150 FILE *write_data_fp;
151 gint write_data_pos;
152 gint write_data_len;
153
154 guint timeout_tag;
155 guint timeout_interval;
156
157 guint idle_tag;
158 guint ping_tag;
159
160 gpointer data;
161
162 /* virtual methods to parse server responses */
163 gint (*recv_msg) (Session *session,
164 const gchar *msg);
165
166 gint (*send_data_finished) (Session *session,
167 guint len);
168 gint (*recv_data_finished) (Session *session,
169 guchar *data,
170 guint len);
171
172 gint (*recv_data_as_file_finished) (Session *session,
173 FILE *fp,
174 guint len);
175
176 void (*destroy) (Session *session);
177
178 /* notification functions */
179 RecvMsgNotify recv_msg_notify;
180 RecvDataProgressiveNotify recv_data_progressive_notify;
181 RecvDataNotify recv_data_notify;
182 SendDataProgressiveNotify send_data_progressive_notify;
183 SendDataNotify send_data_notify;
184
185 gpointer recv_msg_notify_data;
186 gpointer recv_data_progressive_notify_data;
187 gpointer recv_data_notify_data;
188 gpointer send_data_progressive_notify_data;
189 gpointer send_data_notify_data;
190};
191
192void session_init (Session *session);
193gint session_connect (Session *session,
194 const gchar *server,
195 gushort port);
196gint session_connect_full (Session *session,
197 const gchar *server,
198 gushort port,
199 SocksInfo *socks_info);
200gint session_disconnect (Session *session);
201void session_destroy (Session *session);
202gboolean session_is_connected (Session *session);
203
204SessionErrorValue session_get_error (Session *session);
205
206void session_set_access_time (Session *session);
207
208void session_set_timeout (Session *session,
209 guint interval);
210
211void session_set_recv_message_notify (Session *session,
212 RecvMsgNotify notify_func,
213 gpointer data);
214void session_set_recv_data_progressive_notify
215 (Session *session,
216 RecvDataProgressiveNotify notify_func,
217 gpointer data);
218void session_set_recv_data_notify (Session *session,
219 RecvDataNotify notify_func,
220 gpointer data);
221void session_set_send_data_progressive_notify
222 (Session *session,
223 SendDataProgressiveNotify notify_func,
224 gpointer data);
225void session_set_send_data_notify (Session *session,
226 SendDataNotify notify_func,
227 gpointer data);
228
229#if USE_SSL
230gint session_start_tls (Session *session);
231#endif
232
233gint session_send_msg (Session *session,
234 SessionMsgType type,
235 const gchar *msg);
236gint session_recv_msg (Session *session);
237gint session_send_data (Session *session,
238 FILE *data_fp,
239 guint size);
240gint session_recv_data (Session *session,
241 guint size,
242 const gchar *terminator);
243
244gint session_recv_data_as_file (Session *session,
245 guint size,
246 const gchar *terminator);
247
248#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2008 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include <glib.h>
25#include <glib/gi18n.h>
26#include <stdio.h>
27#include <string.h>
28
29#include "smtp.h"
30#include "md5_hmac.h"
31#include "base64.h"
32#include "utils.h"
33
34static void smtp_session_destroy(Session *session);
35
36static gint smtp_from(SMTPSession *session);
37
38static gint smtp_auth(SMTPSession *session);
39static gint smtp_starttls(SMTPSession *session);
40static gint smtp_auth_cram_md5(SMTPSession *session);
41static gint smtp_auth_plain(SMTPSession *session);
42static gint smtp_auth_login(SMTPSession *session);
43
44static gint smtp_ehlo(SMTPSession *session);
45static gint smtp_ehlo_recv(SMTPSession *session, const gchar *msg);
46
47static gint smtp_helo(SMTPSession *session);
48static gint smtp_rcpt(SMTPSession *session);
49static gint smtp_data(SMTPSession *session);
50static gint smtp_send_data(SMTPSession *session);
51/* static gint smtp_rset(SMTPSession *session); */
52static gint smtp_quit(SMTPSession *session);
53static gint smtp_eom(SMTPSession *session);
54
55static gint smtp_session_recv_msg(Session *session, const gchar *msg);
56static gint smtp_session_send_data_finished(Session *session, guint len);
57
58
59Session *smtp_session_new(void)
60{
61 SMTPSession *session;
62
63 session = g_new0(SMTPSession, 1);
64
65 session_init(SESSION(session));
66
67 SESSION(session)->type = SESSION_SMTP;
68
69 SESSION(session)->recv_msg = smtp_session_recv_msg;
70
71 SESSION(session)->recv_data_finished = NULL;
72 SESSION(session)->send_data_finished = smtp_session_send_data_finished;
73
74 SESSION(session)->destroy = smtp_session_destroy;
75
76 session->state = SMTP_READY;
77
78#if USE_SSL
79 session->tls_init_done = FALSE;
80#endif
81
82 session->hostname = NULL;
83 session->user = NULL;
84 session->pass = NULL;
85
86 session->from = NULL;
87 session->to_list = NULL;
88 session->cur_to = NULL;
89
90 session->send_data_fp = NULL;
91 session->send_data_len = 0;
92
93 session->avail_auth_type = 0;
94 session->forced_auth_type = 0;
95 session->auth_type = 0;
96
97 session->error_val = SM_OK;
98 session->error_msg = NULL;
99
100 return SESSION(session);
101}
102
103static void smtp_session_destroy(Session *session)
104{
105 SMTPSession *smtp_session = SMTP_SESSION(session);
106
107 g_free(smtp_session->hostname);
108 g_free(smtp_session->user);
109 g_free(smtp_session->pass);
110 g_free(smtp_session->from);
111
112 if (smtp_session->send_data_fp)
113 fclose(smtp_session->send_data_fp);
114
115 g_free(smtp_session->error_msg);
116}
117
118static gint smtp_from(SMTPSession *session)
119{
120 gchar buf[SMTPBUFSIZE];
121
122 g_return_val_if_fail(session->from != NULL, SM_ERROR);
123
124 session->state = SMTP_FROM;
125
126 if (strchr(session->from, '<'))
127 g_snprintf(buf, sizeof(buf), "MAIL FROM:%s", session->from);
128 else
129 g_snprintf(buf, sizeof(buf), "MAIL FROM:<%s>", session->from);
130
131 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf);
132 log_print("SMTP> %s\n", buf);
133
134 return SM_OK;
135}
136
137static gint smtp_auth(SMTPSession *session)
138{
139
140 g_return_val_if_fail(session->user != NULL, SM_ERROR);
141
142 session->state = SMTP_AUTH;
143
144 if (session->forced_auth_type == SMTPAUTH_CRAM_MD5 ||
145 (session->forced_auth_type == 0 &&
146 (session->avail_auth_type & SMTPAUTH_CRAM_MD5) != 0))
147 smtp_auth_cram_md5(session);
148 else if (session->forced_auth_type == SMTPAUTH_PLAIN ||
149 (session->forced_auth_type == 0 &&
150 (session->avail_auth_type & SMTPAUTH_PLAIN) != 0))
151 smtp_auth_plain(session);
152 else if (session->forced_auth_type == SMTPAUTH_LOGIN ||
153 (session->forced_auth_type == 0 &&
154 (session->avail_auth_type & SMTPAUTH_LOGIN) != 0))
155 smtp_auth_login(session);
156 else {
157 log_warning(_("SMTP AUTH not available\n"));
158 return SM_AUTHFAIL;
159 }
160
161 return SM_OK;
162}
163
164static gint smtp_auth_recv(SMTPSession *session, const gchar *msg)
165{
166 gchar buf[SMTPBUFSIZE];
167
168 switch (session->auth_type) {
169 case SMTPAUTH_LOGIN:
170 session->state = SMTP_AUTH_LOGIN_USER;
171
172 if (!strncmp(msg, "334 ", 4)) {
173 base64_encode(buf, (guchar *)session->user,
174 strlen(session->user));
175
176 session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
177 buf);
178 log_print("ESMTP> [USERID]\n");
179 } else {
180 /* Server rejects AUTH */
181 session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
182 "*");
183 log_print("ESMTP> *\n");
184 }
185 break;
186 case SMTPAUTH_CRAM_MD5:
187 session->state = SMTP_AUTH_CRAM_MD5;
188
189 if (!strncmp(msg, "334 ", 4)) {
190 gchar *response;
191 gchar *response64;
192 guchar *challenge;
193 gint challengelen;
194 gchar hexdigest[33];
195
196 challenge = g_malloc(strlen(msg + 4) + 1);
197 challengelen = base64_decode(challenge, msg + 4, -1);
198 challenge[challengelen] = '\0';
199 log_print("ESMTP< [Decoded: %s]\n", challenge);
200
201 g_snprintf(buf, sizeof(buf), "%s", session->pass);
202 md5_hex_hmac(hexdigest, challenge, challengelen,
203 (guchar *)buf, strlen(buf));
204 g_free(challenge);
205
206 response = g_strdup_printf
207 ("%s %s", session->user, hexdigest);
208 log_print("ESMTP> [Encoded: %s]\n", response);
209
210 response64 = g_malloc((strlen(response) + 3) * 2 + 1);
211 base64_encode(response64, (guchar *)response,
212 strlen(response));
213 g_free(response);
214
215 session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
216 response64);
217 log_print("ESMTP> %s\n", response64);
218 g_free(response64);
219 } else {
220 /* Server rejects AUTH */
221 session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
222 "*");
223 log_print("ESMTP> *\n");
224 }
225 break;
226 case SMTPAUTH_DIGEST_MD5:
227 default:
228 /* stop smtp_auth when no correct authtype */
229 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "*");
230 log_print("ESMTP> *\n");
231 break;
232 }
233
234 return SM_OK;
235}
236
237static gint smtp_auth_login_user_recv(SMTPSession *session, const gchar *msg)
238{
239 gchar buf[SMTPBUFSIZE];
240
241 session->state = SMTP_AUTH_LOGIN_PASS;
242
243 if (!strncmp(msg, "334 ", 4))
244 base64_encode(buf, (guchar *)session->pass,
245 strlen(session->pass));
246 else
247 /* Server rejects AUTH */
248 g_snprintf(buf, sizeof(buf), "*");
249
250 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf);
251 log_print("ESMTP> [PASSWORD]\n");
252
253 return SM_OK;
254}
255
256static gint smtp_ehlo(SMTPSession *session)
257{
258 gchar buf[SMTPBUFSIZE];
259
260 session->state = SMTP_EHLO;
261
262 session->avail_auth_type = 0;
263
264 g_snprintf(buf, sizeof(buf), "EHLO %s",
265 session->hostname ? session->hostname : get_domain_name());
266 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf);
267 log_print("ESMTP> %s\n", buf);
268
269 return SM_OK;
270}
271
272static gint smtp_ehlo_recv(SMTPSession *session, const gchar *msg)
273{
274 if (strncmp(msg, "250", 3) == 0) {
275 const gchar *p = msg;
276 p += 3;
277 if (*p == '-' || *p == ' ') p++;
278 if (g_ascii_strncasecmp(p, "AUTH", 4) == 0 && p[4] != '\0') {
279 p += 5;
280 if (strcasestr(p, "PLAIN"))
281 session->avail_auth_type |= SMTPAUTH_PLAIN;
282 if (strcasestr(p, "LOGIN"))
283 session->avail_auth_type |= SMTPAUTH_LOGIN;
284 if (strcasestr(p, "CRAM-MD5"))
285 session->avail_auth_type |= SMTPAUTH_CRAM_MD5;
286 if (strcasestr(p, "DIGEST-MD5"))
287 session->avail_auth_type |= SMTPAUTH_DIGEST_MD5;
288 }
289 return SM_OK;
290 } else if ((msg[0] == '1' || msg[0] == '2' || msg[0] == '3') &&
291 (msg[3] == ' ' || msg[3] == '\0'))
292 return SM_OK;
293 else if (msg[0] == '5' && msg[1] == '0' &&
294 (msg[2] == '4' || msg[2] == '3' || msg[2] == '1'))
295 return SM_ERROR;
296
297 return SM_ERROR;
298}
299
300static gint smtp_starttls(SMTPSession *session)
301{
302 session->state = SMTP_STARTTLS;
303
304 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "STARTTLS");
305 log_print("ESMTP> STARTTLS\n");
306
307 return SM_OK;
308}
309
310static gint smtp_auth_cram_md5(SMTPSession *session)
311{
312 session->state = SMTP_AUTH;
313 session->auth_type = SMTPAUTH_CRAM_MD5;
314
315 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "AUTH CRAM-MD5");
316 log_print("ESMTP> AUTH CRAM-MD5\n");
317
318 return SM_OK;
319}
320
321static gint smtp_auth_plain(SMTPSession *session)
322{
323 gchar *authstr;
324 gint authlen;
325 gchar *outbuf;
326 gchar *p;
327
328 session->state = SMTP_AUTH_PLAIN;
329 session->auth_type = SMTPAUTH_PLAIN;
330
331 /*
332 * construct the string: \0<user>\0<pass>
333 */
334
335 authlen = 1 + strlen(session->user) + 1 + strlen(session->pass);
336 authstr = g_malloc(authlen + 1);
337
338 p = authstr;
339
340 *p++ = '\0';
341 strcpy(p, session->user);
342 p += strlen(p) + 1;
343 strcpy(p, session->pass);
344
345 outbuf = g_malloc(sizeof("AUTH PLAIN ") + authlen * 2 + 1);
346
347 strcpy(outbuf, "AUTH PLAIN ");
348 p = outbuf + strlen(outbuf);
349 base64_encode(p, (guchar *)authstr, authlen);
350
351 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, outbuf);
352 log_print("ESMTP> AUTH PLAIN ********\n");
353
354 g_free(outbuf);
355 g_free(authstr);
356
357 return SM_OK;
358}
359
360static gint smtp_auth_login(SMTPSession *session)
361{
362 session->state = SMTP_AUTH;
363 session->auth_type = SMTPAUTH_LOGIN;
364
365 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "AUTH LOGIN");
366 log_print("ESMTP> AUTH LOGIN\n");
367
368 return SM_OK;
369}
370
371static gint smtp_helo(SMTPSession *session)
372{
373 gchar buf[SMTPBUFSIZE];
374
375 session->state = SMTP_HELO;
376
377 g_snprintf(buf, sizeof(buf), "HELO %s",
378 session->hostname ? session->hostname : get_domain_name());
379 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf);
380 log_print("SMTP> %s\n", buf);
381
382 return SM_OK;
383}
384
385static gint smtp_rcpt(SMTPSession *session)
386{
387 gchar buf[SMTPBUFSIZE];
388 gchar *to;
389
390 g_return_val_if_fail(session->cur_to != NULL, SM_ERROR);
391
392 session->state = SMTP_RCPT;
393
394 to = (gchar *)session->cur_to->data;
395
396 if (strchr(to, '<'))
397 g_snprintf(buf, sizeof(buf), "RCPT TO:%s", to);
398 else
399 g_snprintf(buf, sizeof(buf), "RCPT TO:<%s>", to);
400 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf);
401 log_print("SMTP> %s\n", buf);
402
403 session->cur_to = session->cur_to->next;
404
405 return SM_OK;
406}
407
408static gint smtp_data(SMTPSession *session)
409{
410 session->state = SMTP_DATA;
411
412 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "DATA");
413 log_print("SMTP> DATA\n");
414
415 return SM_OK;
416}
417
418static gint smtp_send_data(SMTPSession *session)
419{
420 session->state = SMTP_SEND_DATA;
421
422 session_send_data(SESSION(session), session->send_data_fp,
423 session->send_data_len);
424
425 return SM_OK;
426}
427
428#if 0
429static gint smtp_rset(SMTPSession *session)
430{
431 session->state = SMTP_RSET;
432
433 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "RSET");
434 log_print("SMTP> RSET\n");
435
436 return SM_OK;
437}
438#endif
439
440static gint smtp_quit(SMTPSession *session)
441{
442 session->state = SMTP_QUIT;
443
444 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "QUIT");
445 log_print("SMTP> QUIT\n");
446
447 return SM_OK;
448}
449
450static gint smtp_eom(SMTPSession *session)
451{
452 session->state = SMTP_EOM;
453
454 session_send_msg(SESSION(session), SESSION_MSG_NORMAL, ".");
455 log_print("SMTP> . (EOM)\n");
456
457 return SM_OK;
458}
459
460static gint smtp_session_recv_msg(Session *session, const gchar *msg)
461{
462 SMTPSession *smtp_session = SMTP_SESSION(session);
463 gboolean cont = FALSE;
464
465 if (strlen(msg) < 4) {
466 log_warning(_("bad SMTP response\n"));
467 return -1;
468 }
469
470 switch (smtp_session->state) {
471 case SMTP_EHLO:
472 case SMTP_STARTTLS:
473 case SMTP_AUTH:
474 case SMTP_AUTH_PLAIN:
475 case SMTP_AUTH_LOGIN_USER:
476 case SMTP_AUTH_LOGIN_PASS:
477 case SMTP_AUTH_CRAM_MD5:
478 log_print("ESMTP< %s\n", msg);
479 break;
480 default:
481 log_print("SMTP< %s\n", msg);
482 break;
483 }
484
485 if (msg[0] == '5' && msg[1] == '0' &&
486 (msg[2] == '4' || msg[2] == '3' || msg[2] == '1')) {
487 log_warning(_("error occurred on SMTP session\n"));
488 smtp_session->state = SMTP_ERROR;
489 smtp_session->error_val = SM_ERROR;
490 g_free(smtp_session->error_msg);
491 smtp_session->error_msg = g_strdup(msg);
492 return -1;
493 }
494
495 if (!strncmp(msg, "535", 3)) {
496 log_warning(_("error occurred on authentication\n"));
497 smtp_session->state = SMTP_ERROR;
498 smtp_session->error_val = SM_AUTHFAIL;
499 g_free(smtp_session->error_msg);
500 smtp_session->error_msg = g_strdup(msg);
501 return -1;
502 }
503
504 if (msg[0] != '1' && msg[0] != '2' && msg[0] != '3') {
505 log_warning(_("error occurred on SMTP session\n"));
506 smtp_session->state = SMTP_ERROR;
507 smtp_session->error_val = SM_ERROR;
508 g_free(smtp_session->error_msg);
509 smtp_session->error_msg = g_strdup(msg);
510 return -1;
511 }
512
513 if (msg[3] == '-')
514 cont = TRUE;
515 else if (msg[3] != ' ' && msg[3] != '\0') {
516 log_warning(_("bad SMTP response\n"));
517 smtp_session->state = SMTP_ERROR;
518 smtp_session->error_val = SM_UNRECOVERABLE;
519 return -1;
520 }
521
522 /* ignore all multiline responses except for EHLO */
523 if (cont && smtp_session->state != SMTP_EHLO)
524 return session_recv_msg(session);
525
526 switch (smtp_session->state) {
527 case SMTP_READY:
528 case SMTP_CONNECTED:
529#if USE_SSL
530 if (smtp_session->user || session->ssl_type != SSL_NONE)
531#else
532 if (smtp_session->user)
533#endif
534 smtp_ehlo(smtp_session);
535 else
536 smtp_helo(smtp_session);
537 break;
538 case SMTP_HELO:
539 smtp_from(smtp_session);
540 break;
541 case SMTP_EHLO:
542 smtp_ehlo_recv(smtp_session, msg);
543 if (cont == TRUE)
544 break;
545#if USE_SSL
546 if (session->ssl_type == SSL_STARTTLS &&
547 smtp_session->tls_init_done == FALSE) {
548 smtp_starttls(smtp_session);
549 break;
550 }
551#endif
552 if (smtp_session->user) {
553 if (smtp_auth(smtp_session) != SM_OK)
554 smtp_from(smtp_session);
555 } else
556 smtp_from(smtp_session);
557 break;
558 case SMTP_STARTTLS:
559#if USE_SSL
560 if (session_start_tls(session) < 0) {
561 log_warning(_("can't start TLS session\n"));
562 smtp_session->state = SMTP_ERROR;
563 smtp_session->error_val = SM_ERROR;
564 return -1;
565 }
566 smtp_session->tls_init_done = TRUE;
567 smtp_ehlo(smtp_session);
568#endif
569 break;
570 case SMTP_AUTH:
571 smtp_auth_recv(smtp_session, msg);
572 break;
573 case SMTP_AUTH_LOGIN_USER:
574 smtp_auth_login_user_recv(smtp_session, msg);
575 break;
576 case SMTP_AUTH_PLAIN:
577 case SMTP_AUTH_LOGIN_PASS:
578 case SMTP_AUTH_CRAM_MD5:
579 smtp_from(smtp_session);
580 break;
581 case SMTP_FROM:
582 if (smtp_session->cur_to)
583 smtp_rcpt(smtp_session);
584 break;
585 case SMTP_RCPT:
586 if (smtp_session->cur_to)
587 smtp_rcpt(smtp_session);
588 else
589 smtp_data(smtp_session);
590 break;
591 case SMTP_DATA:
592 smtp_send_data(smtp_session);
593 break;
594 case SMTP_EOM:
595 smtp_quit(smtp_session);
596 break;
597 case SMTP_QUIT:
598 session_disconnect(session);
599 break;
600 case SMTP_ERROR:
601 default:
602 log_warning(_("error occurred on SMTP session\n"));
603 smtp_session->error_val = SM_ERROR;
604 return -1;
605 }
606
607 if (cont)
608 return session_recv_msg(session);
609
610 return 0;
611}
612
613static gint smtp_session_send_data_finished(Session *session, guint len)
614{
615 smtp_eom(SMTP_SESSION(session));
616 return 0;
617}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2008 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __SMTP_H__
21#define __SMTP_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28#include <stdio.h>
29
30#include "session.h"
31
32typedef struct _SMTPSession SMTPSession;
33
34#define SMTP_SESSION(obj) ((SMTPSession *)obj)
35
36#define SMTPBUFSIZE 8192
37
38typedef enum
39{
40 SM_OK = 0,
41 SM_ERROR = 128,
42 SM_UNRECOVERABLE = 129,
43 SM_AUTHFAIL = 130
44} SMTPErrorValue;
45
46typedef enum
47{
48 ESMTP_8BITMIME = 1 << 0,
49 ESMTP_SIZE = 1 << 1,
50 ESMTP_ETRN = 1 << 2
51} ESMTPFlag;
52
53typedef enum
54{
55 SMTPAUTH_LOGIN = 1 << 0,
56 SMTPAUTH_CRAM_MD5 = 1 << 1,
57 SMTPAUTH_DIGEST_MD5 = 1 << 2,
58 SMTPAUTH_PLAIN = 1 << 3
59} SMTPAuthType;
60
61typedef enum
62{
63 SMTP_READY,
64 SMTP_CONNECTED,
65 SMTP_HELO,
66 SMTP_EHLO,
67 SMTP_STARTTLS,
68 SMTP_FROM,
69 SMTP_AUTH,
70 SMTP_AUTH_PLAIN,
71 SMTP_AUTH_LOGIN_USER,
72 SMTP_AUTH_LOGIN_PASS,
73 SMTP_AUTH_CRAM_MD5,
74 SMTP_RCPT,
75 SMTP_DATA,
76 SMTP_SEND_DATA,
77 SMTP_EOM,
78 SMTP_RSET,
79 SMTP_QUIT,
80 SMTP_ERROR,
81 SMTP_DISCONNECTED,
82
83 N_SMTP_PHASE
84} SMTPState;
85
86struct _SMTPSession
87{
88 Session session;
89
90 SMTPState state;
91
92 gboolean tls_init_done;
93
94 gchar *hostname;
95
96 gchar *user;
97 gchar *pass;
98
99 gchar *from;
100 GSList *to_list;
101 GSList *cur_to;
102
103 FILE *send_data_fp;
104 gint send_data_len;
105
106 SMTPAuthType avail_auth_type;
107 SMTPAuthType forced_auth_type;
108 SMTPAuthType auth_type;
109
110 SMTPErrorValue error_val;
111 gchar *error_msg;
112};
113
114Session *smtp_session_new (void);
115
116#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include <glib.h>
25#include <sys/time.h>
26#include <sys/types.h>
27#ifdef G_OS_WIN32
28# include <winsock2.h>
29# include <ws2tcpip.h>
30#else
31# if HAVE_SYS_WAIT_H
32# include <sys/wait.h>
33# endif
34# include <sys/socket.h>
35# include <sys/un.h>
36# include <netinet/in.h>
37# include <arpa/inet.h>
38# include <resolv.h>
39# include <netdb.h>
40# include <sys/stat.h>
41#endif /* G_OS_WIN32 */
42#include <unistd.h>
43#include <stdio.h>
44#include <string.h>
45#include <stdarg.h>
46#include <fcntl.h>
47#include <errno.h>
48#include <signal.h>
49#include <setjmp.h>
50#if HAVE_SYS_SELECT_H
51# include <sys/select.h>
52#endif
53
54#include "socket.h"
55#if USE_SSL
56# include "ssl.h"
57#endif
58
59#include "utils.h"
60
61#define BUFFSIZE 8192
62
63#ifdef G_OS_WIN32
64#define SockDesc SOCKET
65#define SOCKET_IS_VALID(s) ((s) != INVALID_SOCKET)
66#else
67#define SockDesc gint
68#define SOCKET_IS_VALID(s) ((s) >= 0)
69#define INVALID_SOCKET (-1)
70#endif
71
72typedef gint (*SockAddrFunc) (GList *addr_list,
73 gpointer data);
74
75typedef struct _SockConnectData SockConnectData;
76typedef struct _SockLookupData SockLookupData;
77typedef struct _SockAddrData SockAddrData;
78typedef struct _SockSource SockSource;
79
80struct _SockConnectData {
81 gint id;
82 gchar *hostname;
83 gushort port;
84#ifdef G_OS_UNIX
85 GList *addr_list;
86 GList *cur_addr;
87 SockLookupData *lookup_data;
88 GIOChannel *channel;
89 guint io_tag;
90#endif /* G_OS_UNIX */
91#if USE_THREADS
92 gint flag;
93 GThread *thread;
94#endif /* G_OS_UNIX */
95 SockInfo *sock;
96 SockConnectFunc func;
97 gpointer data;
98};
99
100struct _SockLookupData {
101 gchar *hostname;
102 pid_t child_pid;
103 GIOChannel *channel;
104 guint io_tag;
105 SockAddrFunc func;
106 gpointer data;
107};
108
109struct _SockAddrData {
110 gint family;
111 gint socktype;
112 gint protocol;
113 gint addr_len;
114 struct sockaddr *addr;
115};
116
117struct _SockSource {
118 GSource parent;
119 SockInfo *sock;
120};
121
122static guint io_timeout = 60;
123
124static GList *sock_connect_data_list = NULL;
125static GList *sock_list = NULL;
126
127static gboolean sock_prepare (GSource *source,
128 gint *timeout);
129static gboolean sock_check (GSource *source);
130static gboolean sock_dispatch (GSource *source,
131 GSourceFunc callback,
132 gpointer user_data);
133
134GSourceFuncs sock_watch_funcs = {
135 sock_prepare,
136 sock_check,
137 sock_dispatch,
138 NULL
139};
140
141static SockInfo *sock_find_from_fd (gint fd);
142
143static gint sock_connect_with_timeout (gint sock,
144 const struct sockaddr *serv_addr,
145 gint addrlen,
146 guint timeout_secs);
147
148#ifndef INET6
149static gint sock_info_connect_by_hostname
150 (SockInfo *sock);
151#else
152#ifdef G_OS_WIN32
153typedef int (*GetAddrInfoFunc) (const char *node,
154 const char *service,
155 const struct addrinfo *hints,
156 struct addrinfo **res);
157typedef void (*FreeAddrInfoFunc) (struct addrinfo *res);
158
159static GetAddrInfoFunc getaddrinfo_func = NULL;
160static FreeAddrInfoFunc freeaddrinfo_func = NULL;
161
162#undef getaddrinfo
163#define getaddrinfo my_getaddrinfo
164#undef freeaddrinfo
165#define freeaddrinfo my_freeaddrinfo
166#endif
167
168static SockDesc sock_info_connect_by_getaddrinfo(SockInfo *sock);
169#endif
170
171#ifdef G_OS_UNIX
172static void sock_address_list_free (GList *addr_list);
173
174static gboolean sock_connect_async_cb (GIOChannel *source,
175 GIOCondition condition,
176 gpointer data);
177static gint sock_connect_async_get_address_info_cb
178 (GList *addr_list,
179 gpointer data);
180
181static gint sock_connect_address_list_async (SockConnectData *conn_data);
182
183static gboolean sock_get_address_info_async_cb (GIOChannel *source,
184 GIOCondition condition,
185 gpointer data);
186static SockLookupData *sock_get_address_info_async
187 (const gchar *hostname,
188 gushort port,
189 SockAddrFunc func,
190 gpointer data);
191static gint sock_get_address_info_async_cancel (SockLookupData *lookup_data);
192#endif /* G_OS_UNIX */
193
194
195gint sock_init(void)
196{
197#ifdef G_OS_WIN32
198 WSADATA wsadata;
199 gint result;
200
201 result = WSAStartup(MAKEWORD(2, 2), &wsadata);
202 if (result != NO_ERROR) {
203 g_warning("WSAStartup() failed\n");
204 return -1;
205 }
206#endif
207 return 0;
208}
209
210gint sock_cleanup(void)
211{
212#ifdef G_OS_WIN32
213 WSACleanup();
214#endif
215 return 0;
216}
217
218gint sock_set_io_timeout(guint sec)
219{
220 io_timeout = sec;
221 return 0;
222}
223
224gint fd_connect_inet(gushort port)
225{
226 SockDesc sock;
227 struct sockaddr_in addr;
228
229 sock = socket(AF_INET, SOCK_STREAM, 0);
230 if (!SOCKET_IS_VALID(sock)) {
231#ifdef G_OS_WIN32
232 g_warning("fd_connect_inet(): socket() failed: %ld\n",
233 WSAGetLastError());
234#else
235 perror("fd_connect_inet(): socket");
236#endif
237 return -1;
238 }
239
240 memset(&addr, 0, sizeof(addr));
241 addr.sin_family = AF_INET;
242 addr.sin_port = htons(port);
243 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
244
245 if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
246 fd_close(sock);
247 return -1;
248 }
249
250 return sock;
251}
252
253gint fd_open_inet(gushort port)
254{
255 SockDesc sock;
256 struct sockaddr_in addr;
257 gint val;
258
259 sock = socket(AF_INET, SOCK_STREAM, 0);
260 if (!SOCKET_IS_VALID(sock)) {
261#ifdef G_OS_WIN32
262 g_warning("fd_open_inet(): socket() failed: %ld\n",
263 WSAGetLastError());
264#else
265 perror("fd_open_inet(): socket");
266#endif
267 return -1;
268 }
269
270 val = 1;
271 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&val,
272 sizeof(val)) < 0) {
273 perror("setsockopt");
274 fd_close(sock);
275 return -1;
276 }
277
278 memset(&addr, 0, sizeof(addr));
279 addr.sin_family = AF_INET;
280 addr.sin_port = htons(port);
281 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
282
283 if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
284 perror("bind");
285 fd_close(sock);
286 return -1;
287 }
288
289 if (listen(sock, 1) < 0) {
290 perror("listen");
291 fd_close(sock);
292 return -1;
293 }
294
295 return sock;
296}
297
298gint fd_connect_unix(const gchar *path)
299{
300#ifdef G_OS_UNIX
301 gint sock;
302 struct sockaddr_un addr;
303
304 sock = socket(PF_UNIX, SOCK_STREAM, 0);
305 if (sock < 0) {
306 perror("fd_connect_unix(): socket");
307 return -1;
308 }
309
310 memset(&addr, 0, sizeof(addr));
311 addr.sun_family = AF_UNIX;
312 strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
313
314 if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
315 fd_close(sock);
316 return -1;
317 }
318
319 return sock;
320#else
321 return -1;
322#endif
323}
324
325gint fd_open_unix(const gchar *path)
326{
327#ifdef G_OS_UNIX
328 gint sock;
329 struct sockaddr_un addr;
330 gint val;
331
332 sock = socket(PF_UNIX, SOCK_STREAM, 0);
333
334 if (sock < 0) {
335 perror("sock_open_unix(): socket");
336 return -1;
337 }
338
339 val = 1;
340 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
341 perror("setsockopt");
342 fd_close(sock);
343 return -1;
344 }
345
346 memset(&addr, 0, sizeof(addr));
347 addr.sun_family = AF_UNIX;
348 strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
349
350 if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
351 perror("bind");
352 fd_close(sock);
353 return -1;
354 }
355
356 if (listen(sock, 1) < 0) {
357 perror("listen");
358 fd_close(sock);
359 return -1;
360 }
361
362 return sock;
363#else
364 return -1;
365#endif
366}
367
368gint fd_accept(gint sock)
369{
370 struct sockaddr_in caddr;
371 guint caddr_len;
372
373 caddr_len = sizeof(caddr);
374 return accept(sock, (struct sockaddr *)&caddr, &caddr_len);
375}
376
377
378SockInfo *sock_new(const gchar *hostname, gushort port)
379{
380 SockInfo *sockinfo;
381
382 sockinfo = g_new0(SockInfo, 1);
383 sockinfo->sock = INVALID_SOCKET;
384 sockinfo->sock_ch = NULL;
385 sockinfo->hostname = g_strdup(hostname);
386 sockinfo->port = port;
387 sockinfo->state = CONN_READY;
388 sockinfo->flags = 0;
389 sockinfo->data = NULL;
390
391 return sockinfo;
392}
393
394static SockInfo *sock_find_from_fd(gint fd)
395{
396 GList *cur;
397
398 for (cur = sock_list; cur != NULL; cur = cur->next) {
399 if (((SockInfo *)cur->data)->sock == fd)
400 return (SockInfo *)cur->data;
401 }
402
403 return NULL;
404}
405
406static gint set_nonblocking_mode(gint fd, gboolean nonblock)
407{
408#ifdef G_OS_WIN32
409 gulong val = nonblock ? 1 : 0;
410 SockInfo *sock;
411
412 if (!nonblock)
413 WSAEventSelect(fd, NULL, 0);
414 if (ioctlsocket(fd, FIONBIO, &val) == SOCKET_ERROR) {
415 g_warning("set_nonblocking_mode(): ioctlsocket() failed: %ld\n",
416 WSAGetLastError());
417 return -1;
418 }
419
420 sock = sock_find_from_fd(fd);
421 if (sock) {
422 if (nonblock) {
423 SOCK_SET_FLAGS(sock->flags, SYL_SOCK_NONBLOCK);
424 } else {
425 SOCK_UNSET_FLAGS(sock->flags, SYL_SOCK_NONBLOCK);
426 }
427 }
428 debug_print("set nonblocking mode to %d\n", nonblock);
429
430 return 0;
431#else
432 gint flags;
433
434 flags = fcntl(fd, F_GETFL, 0);
435 if (flags < 0) {
436 perror("fcntl");
437 return -1;
438 }
439
440 if (nonblock)
441 flags |= O_NONBLOCK;
442 else
443 flags &= ~O_NONBLOCK;
444
445 return fcntl(fd, F_SETFL, flags);
446#endif
447}
448
449gint sock_set_nonblocking_mode(SockInfo *sock, gboolean nonblock)
450{
451 gint ret;
452
453 g_return_val_if_fail(sock != NULL, -1);
454
455 ret = set_nonblocking_mode(sock->sock, nonblock);
456 if (ret == 0) {
457 if (nonblock) {
458 SOCK_SET_FLAGS(sock->flags, SYL_SOCK_NONBLOCK);
459 } else {
460 SOCK_UNSET_FLAGS(sock->flags, SYL_SOCK_NONBLOCK);
461 }
462 }
463
464 return ret;
465}
466
467static gboolean is_nonblocking_mode(gint fd)
468{
469#ifdef G_OS_WIN32
470 SockInfo *sock;
471
472 sock = sock_find_from_fd(fd);
473 if (sock) {
474 return SOCK_IS_NONBLOCK(sock->flags);
475 }
476
477 return FALSE;
478#else
479 gint flags;
480
481 flags = fcntl(fd, F_GETFL, 0);
482 if (flags < 0) {
483 perror("fcntl");
484 return FALSE;
485 }
486
487 return ((flags & O_NONBLOCK) != 0);
488#endif
489}
490
491gboolean sock_is_nonblocking_mode(SockInfo *sock)
492{
493 g_return_val_if_fail(sock != NULL, FALSE);
494
495#ifdef G_OS_WIN32
496 return SOCK_IS_NONBLOCK(sock->flags);
497#else
498 return is_nonblocking_mode(sock->sock);
499#endif
500}
501
502gboolean sock_has_read_data(SockInfo *sock)
503{
504#ifdef G_OS_WIN32
505 gulong val;
506
507#if USE_SSL
508 if (sock->ssl)
509 return TRUE;
510#endif
511 if (ioctlsocket(sock->sock, FIONREAD, &val) < 0) {
512 g_warning("sock_has_read_data(): ioctlsocket() failed: %ld\n",
513 WSAGetLastError());
514 return TRUE;
515 }
516
517 if (val == 0)
518 return FALSE;
519 else
520 return TRUE;
521#else
522 return TRUE;
523#endif
524}
525
526
527static gboolean sock_prepare(GSource *source, gint *timeout)
528{
529 *timeout = 1;
530 return FALSE;
531}
532
533static gboolean sock_check(GSource *source)
534{
535 SockInfo *sock = ((SockSource *)source)->sock;
536 struct timeval timeout = {0, 0};
537 fd_set fds;
538 GIOCondition condition = sock->condition;
539
540#if USE_SSL
541 if (sock->ssl) {
542 if (condition & G_IO_IN) {
543 if (SSL_pending(sock->ssl) > 0)
544 return TRUE;
545 if (SSL_want_write(sock->ssl))
546 condition |= G_IO_OUT;
547 }
548
549 if (condition & G_IO_OUT) {
550 if (SSL_want_read(sock->ssl))
551 condition |= G_IO_IN;
552 }
553 }
554#endif
555
556 FD_ZERO(&fds);
557 FD_SET(sock->sock, &fds);
558
559 select(sock->sock + 1,
560 (condition & G_IO_IN) ? &fds : NULL,
561 (condition & G_IO_OUT) ? &fds : NULL,
562 NULL, &timeout);
563
564 return FD_ISSET(sock->sock, &fds) != 0;
565}
566
567static gboolean sock_dispatch(GSource *source, GSourceFunc callback,
568 gpointer user_data)
569{
570 SockInfo *sock = ((SockSource *)source)->sock;
571
572 return sock->callback(sock, sock->condition, sock->data);
573}
574
575static gboolean sock_watch_cb(GIOChannel *source, GIOCondition condition,
576 gpointer data)
577{
578 SockInfo *sock = (SockInfo *)data;
579
580 if ((condition & sock->condition) == 0)
581 return TRUE;
582
583 return sock->callback(sock, sock->condition, sock->data);
584}
585
586guint sock_add_watch(SockInfo *sock, GIOCondition condition, SockFunc func,
587 gpointer data)
588{
589 sock->callback = func;
590 sock->condition = condition;
591 sock->data = data;
592
593#if USE_SSL
594 if (sock->ssl) {
595 GSource *source;
596
597 source = g_source_new(&sock_watch_funcs, sizeof(SockSource));
598 ((SockSource *)source)->sock = sock;
599 g_source_set_priority(source, G_PRIORITY_DEFAULT);
600 g_source_set_can_recurse(source, FALSE);
601 return g_source_attach(source, NULL);
602 }
603#endif
604
605 return g_io_add_watch(sock->sock_ch, condition, sock_watch_cb, sock);
606}
607
608guint sock_add_watch_poll(SockInfo *sock, GIOCondition condition, SockFunc func,
609 gpointer data)
610{
611 GSource *source;
612
613 sock->callback = func;
614 sock->condition = condition;
615 sock->data = data;
616
617 source = g_source_new(&sock_watch_funcs, sizeof(SockSource));
618 ((SockSource *)source)->sock = sock;
619 g_source_set_priority(source, G_PRIORITY_DEFAULT);
620 g_source_set_can_recurse(source, FALSE);
621
622 return g_source_attach(source, NULL);
623}
624
625static gint fd_check_io(gint fd, GIOCondition cond)
626{
627 struct timeval timeout;
628 fd_set fds;
629 SockInfo *sock;
630
631 sock = sock_find_from_fd(fd);
632 if (sock && !SOCK_IS_CHECK_IO(sock->flags))
633 return 0;
634
635 timeout.tv_sec = io_timeout;
636 timeout.tv_usec = 0;
637
638 FD_ZERO(&fds);
639 FD_SET(fd, &fds);
640
641 if (cond == G_IO_IN) {
642 select(fd + 1, &fds, NULL, NULL,
643 io_timeout > 0 ? &timeout : NULL);
644 } else {
645 select(fd + 1, NULL, &fds, NULL,
646 io_timeout > 0 ? &timeout : NULL);
647 }
648
649 if (FD_ISSET(fd, &fds)) {
650 return 0;
651 } else {
652 g_warning("Socket IO timeout\n");
653 return -1;
654 }
655}
656
657#if defined(G_OS_UNIX) && !defined(USE_THREADS)
658static sigjmp_buf jmpenv;
659
660static void timeout_handler(gint sig)
661{
662 siglongjmp(jmpenv, 1);
663}
664#endif
665
666static gint sock_connect_with_timeout(gint sock,
667 const struct sockaddr *serv_addr,
668 gint addrlen,
669 guint timeout_secs)
670{
671 gint ret;
672
673#ifdef G_OS_WIN32
674 WSAEVENT hevent;
675 gint err;
676 DWORD dwret;
677 WSANETWORKEVENTS events;
678
679 errno = 0;
680
681 hevent = WSACreateEvent();
682 if (hevent == WSA_INVALID_EVENT)
683 return -1;
684
685 ret = WSAEventSelect(sock, hevent, FD_CONNECT);
686 if (ret == SOCKET_ERROR) {
687 g_warning("sock_connect_with_timeout: WSAEventSelect");
688 WSACloseEvent(hevent);
689 return -1;
690 }
691
692 ret = connect(sock, serv_addr, addrlen);
693
694 if (ret == SOCKET_ERROR) {
695 err = WSAGetLastError();
696 if (err != WSAEWOULDBLOCK) {
697 g_warning("sock_connect_with_timeout: connect (%d)", err);
698 ret = -1;
699 goto end;
700 }
701 }
702
703 dwret = WSAWaitForMultipleEvents(1, &hevent, FALSE, timeout_secs * 1000, FALSE);
704 if (dwret == WSA_WAIT_TIMEOUT) {
705 g_warning("sock_connect_with_timeout: timeout");
706 errno = WSAETIMEDOUT;
707 ret = -1;
708 goto end;
709 } else if (dwret != WSA_WAIT_EVENT_0) {
710 g_warning("sock_connect_with_timeout: WSAWaitForMultipleEvents (%d)", dwret);
711 ret = -1;
712 goto end;
713 }
714
715 ret = WSAEnumNetworkEvents(sock, hevent, &events);
716 if (ret == SOCKET_ERROR) {
717 g_warning("sock_connect_with_timeout: WSAEnumNetworkEvents (%d)", ret);
718 ret = -1;
719 goto end;
720 }
721
722 if ((events.lNetworkEvents & FD_CONNECT) &&
723 events.iErrorCode[FD_CONNECT_BIT] == 0) {
724 ret = 0;
725 errno = 0;
726 } else
727 ret = -1;
728
729end:
730 WSAEventSelect(sock, NULL, 0);
731 WSACloseEvent(hevent);
732
733 set_nonblocking_mode(sock, FALSE);
734#else
735 set_nonblocking_mode(sock, TRUE);
736
737 ret = connect(sock, serv_addr, addrlen);
738
739 if (ret < 0) {
740 if (EINPROGRESS == errno) {
741 fd_set fds;
742 struct timeval tv;
743
744 tv.tv_sec = timeout_secs;
745 tv.tv_usec = 0;
746 FD_ZERO(&fds);
747 FD_SET(sock, &fds);
748 do {
749 ret = select(sock + 1, NULL, &fds, NULL, &tv);
750 } while (ret < 0 && EINTR == errno);
751 if (ret < 0) {
752 perror("sock_connect_with_timeout: select");
753 return -1;
754 } else if (ret == 0) {
755 debug_print("sock_connect_with_timeout: timeout\n");
756 errno = ETIMEDOUT;
757 return -1;
758 } else {
759 gint val;
760 guint len;
761
762 if (FD_ISSET(sock, &fds)) {
763 ret = 0;
764 } else {
765 debug_print("sock_connect_with_timeout: fd not set\n");
766 return -1;
767 }
768
769 len = sizeof(val);
770 if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &val, &len) < 0) {
771 perror("sock_connect_with_timeout: getsockopt");
772 return -1;
773 }
774 if (val != 0) {
775 debug_print("sock_connect_with_timeout: getsockopt(SOL_SOCKET, SO_ERROR) returned error: %s\n", g_strerror(val));
776 return -1;
777 }
778 }
779 } else {
780 perror("sock_connect_with_timeout: connect");
781 return -1;
782 }
783 }
784
785 set_nonblocking_mode(sock, FALSE);
786#endif
787
788 return ret;
789}
790
791static void resolver_init(void)
792{
793#ifdef G_OS_UNIX
794 static time_t resolv_conf_mtime = 0;
795 GStatBuf s;
796
797 if (g_stat("/etc/resolv.conf", &s) == 0 &&
798 s.st_mtime != resolv_conf_mtime) {
799 debug_print("Reloading /etc/resolv.conf\n");
800 resolv_conf_mtime = s.st_mtime;
801 res_init();
802 }
803#endif
804}
805
806struct hostent *my_gethostbyname(const gchar *hostname)
807{
808 struct hostent *hp;
809#if defined(G_OS_UNIX) && !defined(USE_THREADS)
810 void (*prev_handler)(gint);
811
812 alarm(0);
813 prev_handler = signal(SIGALRM, timeout_handler);
814 if (sigsetjmp(jmpenv, 1)) {
815 alarm(0);
816 signal(SIGALRM, prev_handler);
817 fprintf(stderr, "%s: host lookup timed out.\n", hostname);
818 errno = 0;
819 return NULL;
820 }
821 alarm(io_timeout);
822#endif
823
824 if ((hp = gethostbyname(hostname)) == NULL) {
825#if defined(G_OS_UNIX) && !defined(USE_THREADS)
826 alarm(0);
827 signal(SIGALRM, prev_handler);
828#endif
829 fprintf(stderr, "%s: unknown host.\n", hostname);
830 errno = 0;
831 return NULL;
832 }
833
834#if defined(G_OS_UNIX) && !defined(USE_THREADS)
835 alarm(0);
836 signal(SIGALRM, prev_handler);
837#endif
838
839 return hp;
840}
841
842static void sock_set_buffer_size(gint sock)
843{
844#ifdef G_OS_WIN32
845 gint val;
846 guint len = sizeof(val);
847
848#define SOCK_BUFFSIZE 32768
849
850 getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&val, &len);
851 if (val < SOCK_BUFFSIZE) {
852 val = SOCK_BUFFSIZE;
853 setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&val, len);
854 }
855 getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&val, &len);
856 if (val < SOCK_BUFFSIZE) {
857 val = SOCK_BUFFSIZE;
858 setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&val, len);
859 }
860 getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&val, &len);
861 debug_print("SO_SNDBUF = %d\n", val);
862 getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&val, &len);
863 debug_print("SO_RCVBUF = %d\n", val);
864
865#undef SOCK_BUFFSIZE
866#endif
867}
868
869#if !defined(INET6) || defined(G_OS_WIN32)
870static gint my_inet_aton(const gchar *hostname, struct in_addr *inp)
871{
872#if HAVE_INET_ATON
873 return inet_aton(hostname, inp);
874#else
875#if HAVE_INET_ADDR
876 guint32 inaddr;
877
878 inaddr = inet_addr(hostname);
879 if (inaddr != -1) {
880 memcpy(inp, &inaddr, sizeof(inaddr));
881 return 1;
882 } else
883 return 0;
884#else
885 return 0;
886#endif
887#endif /* HAVE_INET_ATON */
888}
889#endif /* !defined(INET6) || defined(G_OS_WIN32) */
890
891#ifndef INET6
892static gint sock_info_connect_by_hostname(SockInfo *sock)
893{
894 struct hostent *hp;
895 struct sockaddr_in ad;
896 gint ret;
897
898 g_return_val_if_fail(sock != NULL, -1);
899 g_return_val_if_fail(sock->hostname != NULL && sock->port > 0, -1);
900
901 resolver_init();
902
903 memset(&ad, 0, sizeof(ad));
904 ad.sin_family = AF_INET;
905 ad.sin_port = htons(sock->port);
906
907 if (!my_inet_aton(sock->hostname, &ad.sin_addr)) {
908 if ((hp = my_gethostbyname(sock->hostname)) == NULL) {
909 fprintf(stderr, "%s: unknown host.\n", sock->hostname);
910 errno = 0;
911 sock->state = CONN_LOOKUPFAILED;
912 return -1;
913 }
914
915 if (hp->h_length != 4 && hp->h_length != 8) {
916 fprintf(stderr, "illegal address length received for host %s\n", sock->hostname);
917 errno = 0;
918 sock->state = CONN_LOOKUPFAILED;
919 return -1;
920 }
921
922 memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
923 }
924
925 sock->state = CONN_LOOKUPSUCCESS;
926
927 ret = sock_connect_with_timeout(sock->sock, (struct sockaddr *)&ad,
928 sizeof(ad), io_timeout);
929 if (ret < 0)
930 sock->state = CONN_FAILED;
931 else
932 sock->state = CONN_ESTABLISHED;
933
934 return ret;
935}
936
937#else /* INET6 */
938
939#ifdef G_OS_WIN32
940static gboolean win32_ipv6_supported(void)
941{
942 static gboolean ipv6_checked = FALSE;
943 HMODULE hmodule;
944
945 if (ipv6_checked)
946 return getaddrinfo_func != NULL;
947
948 hmodule = GetModuleHandleA("ws2_32");
949 if (hmodule) {
950 getaddrinfo_func =
951 (GetAddrInfoFunc)GetProcAddress(hmodule, "getaddrinfo");
952 freeaddrinfo_func =
953 (FreeAddrInfoFunc)GetProcAddress(hmodule, "freeaddrinfo");
954 if (!getaddrinfo_func || !freeaddrinfo_func) {
955 getaddrinfo_func = NULL;
956 freeaddrinfo_func = NULL;
957 }
958 }
959
960 if (getaddrinfo_func)
961 debug_print("ws2_32 has IPv6 functions.\n");
962 else
963 debug_print("ws2_32 does not have IPv6 functions.\n");
964
965 ipv6_checked = TRUE;
966 return getaddrinfo_func != NULL;
967}
968
969/* subset of getaddrinfo() */
970static int my_getaddrinfo(const char *node, const char *service,
971 const struct addrinfo *hintp,
972 struct addrinfo **res)
973{
974 struct addrinfo *ai;
975 struct sockaddr_in addr, *paddr;
976 struct addrinfo hints;
977 gint port = 0;
978
979 if (win32_ipv6_supported())
980 return getaddrinfo_func(node, service, hintp, res);
981
982 if (!hintp) {
983 memset(&hints, 0, sizeof(hints));
984 hints.ai_family = AF_INET;
985 hints.ai_socktype = SOCK_STREAM;
986 } else
987 memcpy(&hints, hintp, sizeof(hints));
988
989 if (hints.ai_family != AF_UNSPEC && hints.ai_family != AF_INET)
990 return EAI_FAMILY;
991 if (hints.ai_socktype == 0)
992 hints.ai_socktype = SOCK_STREAM;
993 if (hints.ai_protocol == 0)
994 hints.ai_protocol = IPPROTO_TCP;
995 if (hints.ai_socktype != SOCK_STREAM)
996 return EAI_SOCKTYPE;
997 if (hints.ai_protocol != IPPROTO_TCP)
998 return EAI_SOCKTYPE;
999#if 0
1000 if (!node && !service)
1001 return EAI_NONAME;
1002#endif
1003 if (!node || !service)
1004 return EAI_NONAME;
1005
1006 port = atoi(service);
1007
1008 memset(&addr, 0, sizeof(addr));
1009 addr.sin_family = AF_INET;
1010 addr.sin_port = htons(port);
1011
1012 if (!my_inet_aton(node, &addr.sin_addr)) {
1013 struct hostent *hp;
1014
1015 if ((hp = my_gethostbyname(node)) == NULL) {
1016 fprintf(stderr, "%s: unknown host.\n", node);
1017 errno = 0;
1018 return EAI_NONAME;
1019 }
1020 if (hp->h_length != 4 && hp->h_length != 8) {
1021 fprintf(stderr, "illegal address length received for host %s\n", node);
1022 errno = 0;
1023 return EAI_FAIL;
1024 }
1025
1026 memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
1027 }
1028
1029 ai = g_malloc0(sizeof(struct addrinfo));
1030 paddr = g_malloc(sizeof(struct sockaddr_in));
1031 memcpy(paddr, &addr, sizeof(struct sockaddr_in));
1032
1033 ai->ai_flags = 0;
1034 ai->ai_family = AF_INET;
1035 ai->ai_socktype = hints.ai_socktype;
1036 ai->ai_protocol = hints.ai_protocol;
1037 ai->ai_addrlen = sizeof(struct sockaddr_in);
1038 ai->ai_addr = (struct sockaddr *)paddr;
1039 ai->ai_canonname = NULL;
1040 ai->ai_next = NULL;
1041
1042 *res = ai;
1043
1044 return 0;
1045}
1046
1047static void my_freeaddrinfo(struct addrinfo *res)
1048{
1049 if (win32_ipv6_supported()) {
1050 freeaddrinfo_func(res);
1051 return;
1052 }
1053
1054 if (res) {
1055 g_free(res->ai_addr);
1056 g_free(res);
1057 }
1058}
1059#endif
1060
1061static SockDesc sock_info_connect_by_getaddrinfo(SockInfo *sockinfo)
1062{
1063 SockDesc sock = INVALID_SOCKET;
1064 gint gai_error;
1065 struct addrinfo hints, *res, *ai;
1066 gchar port_str[6];
1067
1068 g_return_val_if_fail(sockinfo != NULL, INVALID_SOCKET);
1069 g_return_val_if_fail(sockinfo->hostname != NULL && sockinfo->port > 0, INVALID_SOCKET);
1070
1071 resolver_init();
1072
1073 memset(&hints, 0, sizeof(hints));
1074 /* hints.ai_flags = AI_CANONNAME; */
1075 hints.ai_family = AF_UNSPEC;
1076 hints.ai_socktype = SOCK_STREAM;
1077 hints.ai_protocol = IPPROTO_TCP;
1078
1079 /* convert port from integer to string. */
1080 g_snprintf(port_str, sizeof(port_str), "%d", sockinfo->port);
1081
1082 if ((gai_error = getaddrinfo(sockinfo->hostname, port_str, &hints, &res)) != 0) {
1083#ifdef G_OS_WIN32
1084 fprintf(stderr, "getaddrinfo for %s:%s failed: errno: %d\n",
1085 sockinfo->hostname, port_str, gai_error);
1086#else
1087 fprintf(stderr, "getaddrinfo for %s:%s failed: %s\n",
1088 sockinfo->hostname, port_str, gai_strerror(gai_error));
1089#endif
1090 debug_print("getaddrinfo failed\n");
1091 sockinfo->state = CONN_LOOKUPFAILED;
1092 return INVALID_SOCKET;
1093 }
1094
1095 for (ai = res; ai != NULL; ai = ai->ai_next) {
1096 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
1097 if (!SOCKET_IS_VALID(sock))
1098 continue;
1099 sock_set_buffer_size(sock);
1100
1101 if (sock_connect_with_timeout
1102 (sock, ai->ai_addr, ai->ai_addrlen, io_timeout) == 0)
1103 break;
1104
1105 fd_close(sock);
1106 }
1107
1108 if (res != NULL)
1109 freeaddrinfo(res);
1110
1111 if (ai == NULL) {
1112 sockinfo->state = CONN_FAILED;
1113 return INVALID_SOCKET;
1114 }
1115
1116 sockinfo->state = CONN_ESTABLISHED;
1117 return sock;
1118}
1119#endif /* !INET6 */
1120
1121SockInfo *sock_connect(const gchar *hostname, gushort port)
1122{
1123 SockInfo *sockinfo;
1124
1125 sockinfo = sock_new(hostname, port);
1126 if (sock_info_connect(sockinfo) < 0) {
1127 sock_close(sockinfo);
1128 return NULL;
1129 }
1130
1131 return sockinfo;
1132}
1133
1134gint sock_info_connect(SockInfo *sockinfo)
1135{
1136 SockDesc sock;
1137#ifndef INET6
1138 gint ret;
1139#endif
1140
1141 g_return_val_if_fail(sockinfo != NULL, -1);
1142 g_return_val_if_fail(sockinfo->hostname != NULL && sockinfo->port > 0,
1143 -1);
1144
1145#ifdef INET6
1146 sock = sock_info_connect_by_getaddrinfo(sockinfo);
1147 if (!SOCKET_IS_VALID(sock)) {
1148 return -1;
1149 }
1150#else
1151 sock = socket(AF_INET, SOCK_STREAM, 0);
1152 if (!SOCKET_IS_VALID(sock)) {
1153#ifdef G_OS_WIN32
1154 g_warning("socket() failed: %ld\n", WSAGetLastError());
1155#else
1156 perror("socket");
1157#endif /* G_OS_WIN32 */
1158 sockinfo->state = CONN_FAILED;
1159 return -1;
1160 }
1161 sock_set_buffer_size(sock);
1162
1163 sockinfo->sock = sock;
1164 if ((ret = sock_info_connect_by_hostname(sockinfo)) < 0) {
1165 if (errno != 0) perror("connect");
1166 fd_close(sock);
1167 sockinfo->sock = INVALID_SOCKET;
1168 return ret;
1169 }
1170#endif /* INET6 */
1171
1172 sockinfo->sock = sock;
1173 sockinfo->sock_ch = g_io_channel_unix_new(sock);
1174 sockinfo->flags = SYL_SOCK_CHECK_IO;
1175
1176 sock_list = g_list_prepend(sock_list, sockinfo);
1177
1178 g_usleep(100000);
1179
1180 return 0;
1181}
1182
1183#ifdef G_OS_UNIX
1184static void sock_address_list_free(GList *addr_list)
1185{
1186 GList *cur;
1187
1188 for (cur = addr_list; cur != NULL; cur = cur->next) {
1189 SockAddrData *addr_data = (SockAddrData *)cur->data;
1190 g_free(addr_data->addr);
1191 g_free(addr_data);
1192 }
1193
1194 g_list_free(addr_list);
1195}
1196
1197/* asynchronous TCP connection */
1198
1199static gboolean sock_connect_async_cb(GIOChannel *source,
1200 GIOCondition condition, gpointer data)
1201{
1202 SockConnectData *conn_data = (SockConnectData *)data;
1203 gint fd;
1204 gint val;
1205 guint len;
1206 SockInfo *sockinfo;
1207
1208 fd = g_io_channel_unix_get_fd(source);
1209
1210 conn_data->io_tag = 0;
1211 conn_data->channel = NULL;
1212 g_io_channel_unref(source);
1213
1214 if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
1215 debug_print("sock_connect_async_cb: condition = %d\n",
1216 condition);
1217 fd_close(fd);
1218 sock_connect_address_list_async(conn_data);
1219 return FALSE;
1220 }
1221
1222 len = sizeof(val);
1223 if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &val, &len) < 0) {
1224 perror("getsockopt");
1225 fd_close(fd);
1226 sock_connect_address_list_async(conn_data);
1227 return FALSE;
1228 }
1229
1230 if (val != 0) {
1231 debug_print("getsockopt(SOL_SOCKET, SO_ERROR) returned error\n");
1232 fd_close(fd);
1233 sock_connect_address_list_async(conn_data);
1234 return FALSE;
1235 }
1236
1237 sockinfo = conn_data->sock;
1238 sockinfo->sock = fd;
1239 sockinfo->sock_ch = g_io_channel_unix_new(fd);
1240 sockinfo->state = CONN_ESTABLISHED;
1241 sockinfo->flags = SYL_SOCK_NONBLOCK;
1242
1243 sock_list = g_list_prepend(sock_list, sockinfo);
1244
1245 conn_data->func(sockinfo, conn_data->data);
1246
1247 conn_data->sock = NULL;
1248 sock_connect_async_cancel(conn_data->id);
1249
1250 return FALSE;
1251}
1252
1253static gint sock_connect_async_get_address_info_cb(GList *addr_list,
1254 gpointer data)
1255{
1256 SockConnectData *conn_data = (SockConnectData *)data;
1257
1258 conn_data->addr_list = addr_list;
1259 conn_data->cur_addr = addr_list;
1260 conn_data->lookup_data = NULL;
1261
1262 return sock_connect_address_list_async(conn_data);
1263}
1264
1265gint sock_connect_async(const gchar *hostname, gushort port,
1266 SockConnectFunc func, gpointer data)
1267{
1268 SockInfo *sock;
1269 gint ret;
1270
1271 sock = sock_new(hostname, port);
1272 ret = sock_info_connect_async(sock, func, data);
1273 if (ret < 0)
1274 sock_close(sock);
1275
1276 return ret;
1277}
1278
1279gint sock_info_connect_async(SockInfo *sock, SockConnectFunc func,
1280 gpointer data)
1281{
1282 static gint id = 1;
1283 SockConnectData *conn_data;
1284
1285 g_return_val_if_fail(sock != NULL, -1);
1286 g_return_val_if_fail(sock->hostname != NULL && sock->port > 0, -1);
1287
1288 conn_data = g_new0(SockConnectData, 1);
1289 conn_data->id = id++;
1290 conn_data->hostname = g_strdup(sock->hostname);
1291 conn_data->port = sock->port;
1292 conn_data->addr_list = NULL;
1293 conn_data->cur_addr = NULL;
1294 conn_data->io_tag = 0;
1295 conn_data->sock = sock;
1296 conn_data->func = func;
1297 conn_data->data = data;
1298
1299 conn_data->lookup_data = sock_get_address_info_async
1300 (sock->hostname, sock->port,
1301 sock_connect_async_get_address_info_cb, conn_data);
1302
1303 if (conn_data->lookup_data == NULL) {
1304 g_free(conn_data->hostname);
1305 g_free(conn_data);
1306 return -1;
1307 }
1308
1309 sock_connect_data_list = g_list_append(sock_connect_data_list,
1310 conn_data);
1311
1312 return conn_data->id;
1313}
1314
1315gint sock_connect_async_cancel(gint id)
1316{
1317 SockConnectData *conn_data = NULL;
1318 GList *cur;
1319
1320 for (cur = sock_connect_data_list; cur != NULL; cur = cur->next) {
1321 if (((SockConnectData *)cur->data)->id == id) {
1322 conn_data = (SockConnectData *)cur->data;
1323 break;
1324 }
1325 }
1326
1327 if (conn_data) {
1328 sock_connect_data_list = g_list_remove(sock_connect_data_list,
1329 conn_data);
1330
1331 if (conn_data->lookup_data)
1332 sock_get_address_info_async_cancel
1333 (conn_data->lookup_data);
1334
1335 if (conn_data->io_tag > 0)
1336 g_source_remove(conn_data->io_tag);
1337 if (conn_data->channel) {
1338 g_io_channel_shutdown(conn_data->channel, FALSE, NULL);
1339 g_io_channel_unref(conn_data->channel);
1340 }
1341 if (conn_data->sock)
1342 sock_close(conn_data->sock);
1343
1344 sock_address_list_free(conn_data->addr_list);
1345 g_free(conn_data->hostname);
1346 g_free(conn_data);
1347 } else {
1348 g_warning("sock_connect_async_cancel: id %d not found.\n", id);
1349 return -1;
1350 }
1351
1352 return 0;
1353}
1354
1355static gint sock_connect_address_list_async(SockConnectData *conn_data)
1356{
1357 SockAddrData *addr_data;
1358 gint sock = -1;
1359
1360 if (conn_data->addr_list == NULL) {
1361 g_warning("sock_connect_address_list_async: "
1362 "DNS lookup for %s failed", conn_data->hostname);
1363 conn_data->sock->state = CONN_LOOKUPFAILED;
1364 conn_data->func(conn_data->sock, conn_data->data);
1365 sock_connect_async_cancel(conn_data->id);
1366 return -1;
1367 }
1368
1369 for (; conn_data->cur_addr != NULL;
1370 conn_data->cur_addr = conn_data->cur_addr->next) {
1371 addr_data = (SockAddrData *)conn_data->cur_addr->data;
1372
1373 if ((sock = socket(addr_data->family, addr_data->socktype,
1374 addr_data->protocol)) < 0) {
1375 perror("socket");
1376 continue;
1377 }
1378
1379 sock_set_buffer_size(sock);
1380 set_nonblocking_mode(sock, TRUE);
1381
1382 if (connect(sock, addr_data->addr, addr_data->addr_len) < 0) {
1383 if (EINPROGRESS == errno) {
1384 break;
1385 } else {
1386 perror("connect");
1387 fd_close(sock);
1388 }
1389 } else
1390 break;
1391 }
1392
1393 if (conn_data->cur_addr == NULL) {
1394 g_warning("sock_connect_address_list_async: "
1395 "connection to %s:%d failed",
1396 conn_data->hostname, conn_data->port);
1397 conn_data->sock->state = CONN_FAILED;
1398 conn_data->func(conn_data->sock, conn_data->data);
1399 sock_connect_async_cancel(conn_data->id);
1400 return -1;
1401 }
1402
1403 debug_print("sock_connect_address_list_async: waiting for connect\n");
1404
1405 conn_data->cur_addr = conn_data->cur_addr->next;
1406
1407 conn_data->channel = g_io_channel_unix_new(sock);
1408 conn_data->io_tag = g_io_add_watch
1409 (conn_data->channel, G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
1410 sock_connect_async_cb, conn_data);
1411
1412 return 0;
1413}
1414
1415static gint sock_kill_process(pid_t pid)
1416{
1417 pid_t ret = (pid_t)-1;
1418
1419 kill(pid, SIGKILL);
1420
1421 while (ret == (pid_t)-1) {
1422 if ((ret = waitpid(pid, NULL, 0)) != pid) {
1423 if (ret == (pid_t)-1 && errno != EINTR) {
1424 perror("sock_kill_process(): waitpid");
1425 break;
1426 }
1427 }
1428 }
1429
1430 return (gint)pid;
1431}
1432
1433/* asynchronous DNS lookup */
1434
1435static gboolean sock_get_address_info_async_cb(GIOChannel *source,
1436 GIOCondition condition,
1437 gpointer data)
1438{
1439 SockLookupData *lookup_data = (SockLookupData *)data;
1440 GList *addr_list = NULL;
1441 SockAddrData *addr_data;
1442 gsize bytes_read;
1443 gint ai_member[4];
1444 struct sockaddr *addr;
1445
1446 for (;;) {
1447 if (g_io_channel_read(source, (gchar *)ai_member,
1448 sizeof(ai_member), &bytes_read)
1449 != G_IO_ERROR_NONE) {
1450 g_warning("sock_get_address_info_async_cb: "
1451 "address length read error\n");
1452 break;
1453 }
1454
1455 if (bytes_read == 0 || bytes_read != sizeof(ai_member))
1456 break;
1457
1458 if (ai_member[0] == AF_UNSPEC) {
1459 g_warning("DNS lookup failed");
1460 break;
1461 }
1462
1463 addr = g_malloc(ai_member[3]);
1464 if (g_io_channel_read(source, (gchar *)addr, ai_member[3],
1465 &bytes_read)
1466 != G_IO_ERROR_NONE) {
1467 g_warning("sock_get_address_info_async_cb: "
1468 "address data read error\n");
1469 g_free(addr);
1470 break;
1471 }
1472
1473 if (bytes_read != ai_member[3]) {
1474 g_warning("sock_get_address_info_async_cb: "
1475 "incomplete address data\n");
1476 g_free(addr);
1477 break;
1478 }
1479
1480 addr_data = g_new0(SockAddrData, 1);
1481 addr_data->family = ai_member[0];
1482 addr_data->socktype = ai_member[1];
1483 addr_data->protocol = ai_member[2];
1484 addr_data->addr_len = ai_member[3];
1485 addr_data->addr = addr;
1486
1487 addr_list = g_list_append(addr_list, addr_data);
1488 }
1489
1490 g_io_channel_shutdown(source, FALSE, NULL);
1491 g_io_channel_unref(source);
1492
1493 sock_kill_process(lookup_data->child_pid);
1494
1495 lookup_data->func(addr_list, lookup_data->data);
1496
1497 g_free(lookup_data->hostname);
1498 g_free(lookup_data);
1499
1500 return FALSE;
1501}
1502
1503static SockLookupData *sock_get_address_info_async(const gchar *hostname,
1504 gushort port,
1505 SockAddrFunc func,
1506 gpointer data)
1507{
1508 SockLookupData *lookup_data = NULL;
1509 gint pipe_fds[2];
1510 pid_t pid;
1511
1512 resolver_init();
1513
1514 if (pipe(pipe_fds) < 0) {
1515 perror("pipe");
1516 func(NULL, data);
1517 return NULL;
1518 }
1519
1520 if ((pid = fork()) < 0) {
1521 perror("fork");
1522 func(NULL, data);
1523 return NULL;
1524 }
1525
1526 /* child process */
1527 if (pid == 0) {
1528#ifdef INET6
1529 gint gai_err;
1530 struct addrinfo hints, *res, *ai;
1531 gchar port_str[6];
1532#else /* !INET6 */
1533 struct hostent *hp;
1534 gchar **addr_list_p;
1535 struct sockaddr_in ad;
1536#endif /* INET6 */
1537 gint ai_member[4] = {AF_UNSPEC, 0, 0, 0};
1538
1539 close(pipe_fds[0]);
1540
1541#ifdef INET6
1542 memset(&hints, 0, sizeof(hints));
1543 /* hints.ai_flags = AI_CANONNAME; */
1544 hints.ai_family = AF_UNSPEC;
1545 hints.ai_socktype = SOCK_STREAM;
1546 hints.ai_protocol = IPPROTO_TCP;
1547
1548 g_snprintf(port_str, sizeof(port_str), "%d", port);
1549
1550 gai_err = getaddrinfo(hostname, port_str, &hints, &res);
1551 if (gai_err != 0) {
1552 g_warning("getaddrinfo for %s:%s failed: %s",
1553 hostname, port_str, gai_strerror(gai_err));
1554 fd_write_all(pipe_fds[1], (gchar *)ai_member,
1555 sizeof(ai_member));
1556 close(pipe_fds[1]);
1557 _exit(1);
1558 }
1559
1560 for (ai = res; ai != NULL; ai = ai->ai_next) {
1561 ai_member[0] = ai->ai_family;
1562 ai_member[1] = ai->ai_socktype;
1563 ai_member[2] = ai->ai_protocol;
1564 ai_member[3] = ai->ai_addrlen;
1565
1566 fd_write_all(pipe_fds[1], (gchar *)ai_member,
1567 sizeof(ai_member));
1568 fd_write_all(pipe_fds[1], (gchar *)ai->ai_addr,
1569 ai->ai_addrlen);
1570 }
1571
1572 if (res != NULL)
1573 freeaddrinfo(res);
1574#else /* !INET6 */
1575 hp = my_gethostbyname(hostname);
1576 if (hp == NULL || hp->h_addrtype != AF_INET) {
1577 fd_write_all(pipe_fds[1], (gchar *)ai_member,
1578 sizeof(ai_member));
1579 close(pipe_fds[1]);
1580 _exit(1);
1581 }
1582
1583 ai_member[0] = AF_INET;
1584 ai_member[1] = SOCK_STREAM;
1585 ai_member[2] = IPPROTO_TCP;
1586 ai_member[3] = sizeof(ad);
1587
1588 memset(&ad, 0, sizeof(ad));
1589 ad.sin_family = AF_INET;
1590 ad.sin_port = htons(port);
1591
1592 for (addr_list_p = hp->h_addr_list; *addr_list_p != NULL;
1593 addr_list_p++) {
1594 memcpy(&ad.sin_addr, *addr_list_p, hp->h_length);
1595 fd_write_all(pipe_fds[1], (gchar *)ai_member,
1596 sizeof(ai_member));
1597 fd_write_all(pipe_fds[1], (gchar *)&ad, sizeof(ad));
1598 }
1599#endif /* INET6 */
1600
1601 close(pipe_fds[1]);
1602
1603 _exit(0);
1604 } else {
1605 close(pipe_fds[1]);
1606
1607 lookup_data = g_new0(SockLookupData, 1);
1608 lookup_data->hostname = g_strdup(hostname);
1609 lookup_data->child_pid = pid;
1610 lookup_data->func = func;
1611 lookup_data->data = data;
1612
1613 lookup_data->channel = g_io_channel_unix_new(pipe_fds[0]);
1614 lookup_data->io_tag = g_io_add_watch
1615 (lookup_data->channel, G_IO_IN,
1616 sock_get_address_info_async_cb, lookup_data);
1617 }
1618
1619 return lookup_data;
1620}
1621
1622static gint sock_get_address_info_async_cancel(SockLookupData *lookup_data)
1623{
1624 if (lookup_data->io_tag > 0)
1625 g_source_remove(lookup_data->io_tag);
1626 if (lookup_data->channel) {
1627 g_io_channel_shutdown(lookup_data->channel, FALSE, NULL);
1628 g_io_channel_unref(lookup_data->channel);
1629 }
1630
1631 if (lookup_data->child_pid > 0)
1632 sock_kill_process(lookup_data->child_pid);
1633
1634 g_free(lookup_data->hostname);
1635 g_free(lookup_data);
1636
1637 return 0;
1638}
1639#endif /* G_OS_UNIX */
1640
1641#if USE_THREADS
1642static gpointer sock_connect_async_func(gpointer data)
1643{
1644 SockConnectData *conn_data = (SockConnectData *)data;
1645 gint ret;
1646
1647 ret = sock_info_connect(conn_data->sock);
1648
1649 if (ret == 0) {
1650 debug_print("sock_connect_async_func: connected\n");
1651 } else {
1652 if (conn_data->sock->state == CONN_LOOKUPFAILED)
1653 debug_print("sock_connect_async_func: DNS lookup failed\n");
1654 else
1655 debug_print("sock_connect_async_func: connection failed\n");
1656 }
1657
1658 g_atomic_int_set(&conn_data->flag, 1);
1659 g_main_context_wakeup(NULL);
1660
1661 debug_print("sock_connect_async_func: exit\n");
1662 return GINT_TO_POINTER(ret);
1663}
1664
1665gint sock_connect_async_thread(const gchar *hostname, gushort port)
1666{
1667 SockInfo *sock;
1668 gint ret;
1669
1670 sock = sock_new(hostname, port);
1671 ret = sock_info_connect_async_thread(sock);
1672 if (ret < 0)
1673 sock_close(sock);
1674
1675 return ret;
1676}
1677
1678gint sock_info_connect_async_thread(SockInfo *sock)
1679{
1680 static gint id = 1;
1681 SockConnectData *data;
1682
1683 g_return_val_if_fail(sock != NULL, -1);
1684 g_return_val_if_fail(sock->hostname != NULL && sock->port > 0, -1);
1685
1686 data = g_new0(SockConnectData, 1);
1687 data->id = id++;
1688 data->hostname = g_strdup(sock->hostname);
1689 data->port = sock->port;
1690 data->flag = 0;
1691 data->sock = sock;
1692
1693 data->thread = g_thread_create(sock_connect_async_func, data, TRUE,
1694 NULL);
1695 if (!data->thread) {
1696 g_free(data->hostname);
1697 g_free(data);
1698 return -1;
1699 }
1700
1701 sock_connect_data_list = g_list_append(sock_connect_data_list, data);
1702
1703 return data->id;
1704}
1705
1706gint sock_connect_async_thread_wait(gint id, SockInfo **sock)
1707{
1708 gint ret;
1709
1710 *sock = NULL;
1711 ret = sock_info_connect_async_thread_wait(id, sock);
1712 if (ret < 0) {
1713 if (*sock) {
1714 sock_close(*sock);
1715 *sock = NULL;
1716 }
1717 }
1718
1719 return ret;
1720}
1721
1722gint sock_info_connect_async_thread_wait(gint id, SockInfo **sock)
1723{
1724 SockConnectData *conn_data = NULL;
1725 GList *cur;
1726 gint ret;
1727
1728 for (cur = sock_connect_data_list; cur != NULL; cur = cur->next) {
1729 if (((SockConnectData *)cur->data)->id == id) {
1730 conn_data = (SockConnectData *)cur->data;
1731 break;
1732 }
1733 }
1734
1735 if (!conn_data) {
1736 g_warning("sock_info_connect_async_thread_wait: id %d not found.", id);
1737 return -1;
1738 }
1739
1740 debug_print("sock_connect_async_thread_wait: waiting thread\n");
1741 while (g_atomic_int_get(&conn_data->flag) == 0)
1742 event_loop_iterate();
1743
1744 ret = GPOINTER_TO_INT(g_thread_join(conn_data->thread));
1745 debug_print("sock_info_connect_async_thread_wait: thread exited with status %d\n", ret);
1746
1747 if (sock)
1748 *sock = conn_data->sock;
1749
1750 sock_connect_data_list = g_list_remove(sock_connect_data_list,
1751 conn_data);
1752 g_free(conn_data->hostname);
1753 g_free(conn_data);
1754
1755 return ret;
1756}
1757#endif /* USE_THREADS */
1758
1759gint sock_printf(SockInfo *sock, const gchar *format, ...)
1760{
1761 va_list args;
1762 gchar buf[BUFFSIZE];
1763
1764 va_start(args, format);
1765 g_vsnprintf(buf, sizeof(buf), format, args);
1766 va_end(args);
1767
1768 return sock_write_all(sock, buf, strlen(buf));
1769}
1770
1771#ifdef G_OS_WIN32
1772static void sock_set_errno_from_last_error(gint error)
1773{
1774 switch (error) {
1775 case WSAEWOULDBLOCK:
1776 errno = EAGAIN;
1777 break;
1778 default:
1779 debug_print("last error = %d\n", error);
1780 errno = 0;
1781 break;
1782 }
1783}
1784#endif
1785
1786gint sock_read(SockInfo *sock, gchar *buf, gint len)
1787{
1788 g_return_val_if_fail(sock != NULL, -1);
1789
1790#if USE_SSL
1791 if (sock->ssl)
1792 return ssl_read(sock->ssl, buf, len);
1793#endif
1794 return fd_read(sock->sock, buf, len);
1795}
1796
1797gint fd_read(gint fd, gchar *buf, gint len)
1798{
1799#ifdef G_OS_WIN32
1800 return fd_recv(fd, buf, len, 0);
1801#else
1802 if (fd_check_io(fd, G_IO_IN) < 0)
1803 return -1;
1804
1805 return read(fd, buf, len);
1806#endif
1807}
1808
1809#if USE_SSL
1810gint ssl_read(SSL *ssl, gchar *buf, gint len)
1811{
1812 gint err, ret;
1813
1814 errno = 0;
1815
1816 if (SSL_pending(ssl) == 0) {
1817 if (fd_check_io(SSL_get_rfd(ssl), G_IO_IN) < 0)
1818 return -1;
1819 }
1820
1821 ret = SSL_read(ssl, buf, len);
1822
1823 switch ((err = SSL_get_error(ssl, ret))) {
1824 case SSL_ERROR_NONE:
1825 return ret;
1826 case SSL_ERROR_WANT_READ:
1827 case SSL_ERROR_WANT_WRITE:
1828 errno = EAGAIN;
1829 return -1;
1830 case SSL_ERROR_ZERO_RETURN:
1831 return 0;
1832 default:
1833 g_warning("SSL_read() returned error %d, ret = %d\n", err, ret);
1834 if (ret == 0)
1835 return 0;
1836#ifdef G_OS_WIN32
1837 errno = EIO;
1838#endif
1839 return -1;
1840 }
1841}
1842#endif
1843
1844gint sock_write(SockInfo *sock, const gchar *buf, gint len)
1845{
1846 g_return_val_if_fail(sock != NULL, -1);
1847
1848#if USE_SSL
1849 if (sock->ssl)
1850 return ssl_write(sock->ssl, buf, len);
1851#endif
1852 return fd_write(sock->sock, buf, len);
1853}
1854
1855gint fd_write(gint fd, const gchar *buf, gint len)
1856{
1857#ifdef G_OS_WIN32
1858 gint ret;
1859#endif
1860 if (fd_check_io(fd, G_IO_OUT) < 0)
1861 return -1;
1862
1863#ifdef G_OS_WIN32
1864 ret = send(fd, buf, len, 0);
1865 if (ret == SOCKET_ERROR) {
1866 gint err;
1867 err = WSAGetLastError();
1868 sock_set_errno_from_last_error(err);
1869 if (err != WSAEWOULDBLOCK)
1870 g_warning("fd_write() failed with %d (errno = %d)\n",
1871 err, errno);
1872 }
1873 return ret;
1874#else
1875 return write(fd, buf, len);
1876#endif
1877}
1878
1879#if USE_SSL
1880gint ssl_write(SSL *ssl, const gchar *buf, gint len)
1881{
1882 gint ret;
1883
1884 ret = SSL_write(ssl, buf, len);
1885
1886 switch (SSL_get_error(ssl, ret)) {
1887 case SSL_ERROR_NONE:
1888 return ret;
1889 case SSL_ERROR_WANT_READ:
1890 case SSL_ERROR_WANT_WRITE:
1891 errno = EAGAIN;
1892 return -1;
1893 default:
1894 return -1;
1895 }
1896}
1897#endif
1898
1899gint sock_write_all(SockInfo *sock, const gchar *buf, gint len)
1900{
1901 g_return_val_if_fail(sock != NULL, -1);
1902
1903#if USE_SSL
1904 if (sock->ssl)
1905 return ssl_write_all(sock->ssl, buf, len);
1906#endif
1907 return fd_write_all(sock->sock, buf, len);
1908}
1909
1910gint fd_write_all(gint fd, const gchar *buf, gint len)
1911{
1912 gint n, wrlen = 0;
1913
1914 while (len) {
1915 n = fd_write(fd, buf, len);
1916 if (n <= 0)
1917 return -1;
1918 len -= n;
1919 wrlen += n;
1920 buf += n;
1921 }
1922
1923 return wrlen;
1924}
1925
1926#if USE_SSL
1927gint ssl_write_all(SSL *ssl, const gchar *buf, gint len)
1928{
1929 gint n, wrlen = 0;
1930
1931 while (len) {
1932 n = ssl_write(ssl, buf, len);
1933 if (n <= 0)
1934 return -1;
1935 len -= n;
1936 wrlen += n;
1937 buf += n;
1938 }
1939
1940 return wrlen;
1941}
1942#endif
1943
1944gint fd_recv(gint fd, gchar *buf, gint len, gint flags)
1945{
1946#ifdef G_OS_WIN32
1947 gint ret;
1948#endif
1949 if (fd_check_io(fd, G_IO_IN) < 0)
1950 return -1;
1951
1952#ifdef G_OS_WIN32
1953 ret = recv(fd, buf, len, flags);
1954 if (ret == SOCKET_ERROR) {
1955 gint err;
1956 err = WSAGetLastError();
1957 sock_set_errno_from_last_error(err);
1958 if (err != WSAEWOULDBLOCK)
1959 g_warning("fd_recv(): failed with %d (errno = %d)\n",
1960 err, errno);
1961 }
1962 return ret;
1963#else
1964 return recv(fd, buf, len, flags);
1965#endif
1966}
1967
1968gint fd_gets(gint fd, gchar *buf, gint len)
1969{
1970 gchar *newline, *bp = buf;
1971 gint n;
1972
1973 if (--len < 1)
1974 return -1;
1975 do {
1976 if ((n = fd_recv(fd, bp, len, MSG_PEEK)) <= 0)
1977 return -1;
1978 if ((newline = memchr(bp, '\n', n)) != NULL)
1979 n = newline - bp + 1;
1980 if ((n = fd_read(fd, bp, n)) < 0)
1981 return -1;
1982 bp += n;
1983 len -= n;
1984 } while (!newline && len);
1985
1986 *bp = '\0';
1987 return bp - buf;
1988}
1989
1990#if USE_SSL
1991gint ssl_gets(SSL *ssl, gchar *buf, gint len)
1992{
1993 gchar *newline, *bp = buf;
1994 gint n;
1995
1996 if (--len < 1)
1997 return -1;
1998 do {
1999 if ((n = ssl_peek(ssl, bp, len)) <= 0)
2000 return -1;
2001 if ((newline = memchr(bp, '\n', n)) != NULL)
2002 n = newline - bp + 1;
2003 if ((n = ssl_read(ssl, bp, n)) < 0)
2004 return -1;
2005 bp += n;
2006 len -= n;
2007 } while (!newline && len);
2008
2009 *bp = '\0';
2010 return bp - buf;
2011}
2012#endif
2013
2014gint sock_gets(SockInfo *sock, gchar *buf, gint len)
2015{
2016 g_return_val_if_fail(sock != NULL, -1);
2017
2018#if USE_SSL
2019 if (sock->ssl)
2020 return ssl_gets(sock->ssl, buf, len);
2021#endif
2022 return fd_gets(sock->sock, buf, len);
2023}
2024
2025gint fd_getline(gint fd, gchar **line)
2026{
2027 gchar buf[BUFFSIZE];
2028 gchar *str = NULL;
2029 gint len;
2030 gulong size = 0;
2031 gulong cur_offset = 0;
2032
2033 while ((len = fd_gets(fd, buf, sizeof(buf))) > 0) {
2034 size += len;
2035 str = g_realloc(str, size + 1);
2036 memcpy(str + cur_offset, buf, len + 1);
2037 cur_offset += len;
2038 if (buf[len - 1] == '\n')
2039 break;
2040 }
2041
2042 *line = str;
2043
2044 if (!str)
2045 return -1;
2046 else
2047 return (gint)size;
2048}
2049
2050#if USE_SSL
2051gint ssl_getline(SSL *ssl, gchar **line)
2052{
2053 gchar buf[BUFFSIZE];
2054 gchar *str = NULL;
2055 gint len;
2056 gulong size = 0;
2057 gulong cur_offset = 0;
2058
2059 while ((len = ssl_gets(ssl, buf, sizeof(buf))) > 0) {
2060 size += len;
2061 str = g_realloc(str, size + 1);
2062 memcpy(str + cur_offset, buf, len + 1);
2063 cur_offset += len;
2064 if (buf[len - 1] == '\n')
2065 break;
2066 }
2067
2068 *line = str;
2069
2070 if (!str)
2071 return -1;
2072 else
2073 return (gint)size;
2074}
2075#endif
2076
2077gint sock_getline(SockInfo *sock, gchar **line)
2078{
2079 g_return_val_if_fail(sock != NULL, -1);
2080 g_return_val_if_fail(line != NULL, -1);
2081
2082#if USE_SSL
2083 if (sock->ssl)
2084 return ssl_getline(sock->ssl, line);
2085#endif
2086 return fd_getline(sock->sock, line);
2087}
2088
2089gint sock_puts(SockInfo *sock, const gchar *buf)
2090{
2091 gint ret;
2092
2093 if ((ret = sock_write_all(sock, buf, strlen(buf))) < 0)
2094 return ret;
2095 return sock_write_all(sock, "\r\n", 2);
2096}
2097
2098/* peek at the socket data without actually reading it */
2099#if USE_SSL
2100gint ssl_peek(SSL *ssl, gchar *buf, gint len)
2101{
2102 gint err, ret;
2103
2104 if (SSL_pending(ssl) == 0) {
2105 if (fd_check_io(SSL_get_rfd(ssl), G_IO_IN) < 0)
2106 return -1;
2107 }
2108
2109 ret = SSL_peek(ssl, buf, len);
2110
2111 switch ((err = SSL_get_error(ssl, ret))) {
2112 case SSL_ERROR_NONE:
2113 return ret;
2114 case SSL_ERROR_WANT_READ:
2115 case SSL_ERROR_WANT_WRITE:
2116 errno = EAGAIN;
2117 return -1;
2118 case SSL_ERROR_ZERO_RETURN:
2119 return 0;
2120 default:
2121 g_warning("SSL_peek() returned error %d, ret = %d\n", err, ret);
2122 if (ret == 0)
2123 return 0;
2124 return -1;
2125 }
2126}
2127#endif
2128
2129gint sock_peek(SockInfo *sock, gchar *buf, gint len)
2130{
2131 g_return_val_if_fail(sock != NULL, -1);
2132
2133#if USE_SSL
2134 if (sock->ssl)
2135 return ssl_peek(sock->ssl, buf, len);
2136#endif
2137 return fd_recv(sock->sock, buf, len, MSG_PEEK);
2138}
2139
2140gint sock_close(SockInfo *sock)
2141{
2142 GList *cur;
2143
2144 if (!sock)
2145 return 0;
2146
2147 debug_print("sock_close: %s:%u (%p)\n", sock->hostname ? sock->hostname : "(none)", sock->port, sock);
2148
2149#if USE_SSL
2150 if (sock->ssl)
2151 ssl_done_socket(sock);
2152#endif
2153
2154 if (sock->sock_ch) {
2155 g_io_channel_shutdown(sock->sock_ch, FALSE, NULL);
2156 g_io_channel_unref(sock->sock_ch);
2157 }
2158
2159 for (cur = sock_list; cur != NULL; cur = cur->next) {
2160 if ((SockInfo *)cur->data == sock) {
2161 sock_list = g_list_remove(sock_list, sock);
2162 break;
2163 }
2164 }
2165
2166 g_free(sock->hostname);
2167 g_free(sock);
2168
2169 return 0;
2170}
2171
2172gint fd_close(gint fd)
2173{
2174#ifdef G_OS_WIN32
2175 return closesocket(fd);
2176#else
2177 return close(fd);
2178#endif
2179}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __SOCKET_H__
21#define __SOCKET_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28#if HAVE_NETDB_H
29# include <netdb.h>
30#endif
31#ifdef G_OS_WIN32
32# include <winsock2.h>
33#endif
34
35typedef struct _SockInfo SockInfo;
36
37#if USE_SSL
38# include "ssl.h"
39#endif
40
41typedef enum
42{
43 CONN_READY,
44 CONN_LOOKUPSUCCESS,
45 CONN_ESTABLISHED,
46 CONN_LOOKUPFAILED,
47 CONN_FAILED
48} ConnectionState;
49
50typedef enum
51{
52 SYL_SOCK_NONBLOCK = 1 << 0,
53 SYL_SOCK_CHECK_IO = 1 << 1
54} SockFlags;
55
56#define SOCK_SET_FLAGS(flags, set) { (flags) |= (set); }
57#define SOCK_UNSET_FLAGS(flags, set) { (flags) &= ~(set); }
58#define SOCK_IS_NONBLOCK(flags) ((flags & SYL_SOCK_NONBLOCK) != 0)
59#define SOCK_IS_CHECK_IO(flags) ((flags & SYL_SOCK_CHECK_IO) != 0)
60
61typedef gint (*SockConnectFunc) (SockInfo *sock,
62 gpointer data);
63typedef gboolean (*SockFunc) (SockInfo *sock,
64 GIOCondition condition,
65 gpointer data);
66
67struct _SockInfo
68{
69 gint sock;
70#if USE_SSL
71 SSL *ssl;
72#else
73 gpointer ssl;
74#endif
75 GIOChannel *sock_ch;
76
77 gchar *hostname;
78 gushort port;
79 ConnectionState state;
80 SockFlags flags;
81 gpointer data;
82
83 SockFunc callback;
84 GIOCondition condition;
85};
86
87gint sock_init (void);
88gint sock_cleanup (void);
89
90gint sock_set_io_timeout (guint sec);
91
92SockInfo *sock_new (const gchar *hostname, gushort port);
93
94gint sock_set_nonblocking_mode (SockInfo *sock, gboolean nonblock);
95gboolean sock_is_nonblocking_mode (SockInfo *sock);
96
97gboolean sock_has_read_data (SockInfo *sock);
98
99guint sock_add_watch (SockInfo *sock, GIOCondition condition,
100 SockFunc func, gpointer data);
101guint sock_add_watch_poll (SockInfo *sock, GIOCondition condition,
102 SockFunc func, gpointer data);
103
104struct hostent *my_gethostbyname (const gchar *hostname);
105
106SockInfo *sock_connect (const gchar *hostname, gushort port);
107#ifdef G_OS_UNIX
108gint sock_connect_async (const gchar *hostname, gushort port,
109 SockConnectFunc func, gpointer data);
110gint sock_connect_async_cancel (gint id);
111#endif
112#if USE_THREADS
113gint sock_connect_async_thread (const gchar *hostname, gushort port);
114gint sock_connect_async_thread_wait (gint id, SockInfo **sock);
115#endif
116
117gint sock_info_connect (SockInfo *sock);
118#ifdef G_OS_UNIX
119gint sock_info_connect_async (SockInfo *sock,
120 SockConnectFunc func, gpointer data);
121#endif
122#if USE_THREADS
123gint sock_info_connect_async_thread (SockInfo *sock);
124gint sock_info_connect_async_thread_wait(gint id, SockInfo **sock);
125#endif
126
127/* Basic I/O functions */
128gint sock_printf (SockInfo *sock, const gchar *format, ...)
129 G_GNUC_PRINTF(2, 3);
130gint sock_read (SockInfo *sock, gchar *buf, gint len);
131gint sock_write (SockInfo *sock, const gchar *buf, gint len);
132gint sock_write_all (SockInfo *sock, const gchar *buf, gint len);
133gint sock_gets (SockInfo *sock, gchar *buf, gint len);
134gint sock_getline (SockInfo *sock, gchar **line);
135gint sock_puts (SockInfo *sock, const gchar *buf);
136gint sock_peek (SockInfo *sock, gchar *buf, gint len);
137gint sock_close (SockInfo *sock);
138
139/* Functions to directly work on FD. They are needed for pipes */
140gint fd_connect_inet (gushort port);
141gint fd_open_inet (gushort port);
142gint fd_connect_unix (const gchar *path);
143gint fd_open_unix (const gchar *path);
144gint fd_accept (gint sock);
145
146gint fd_read (gint sock, gchar *buf, gint len);
147gint fd_write (gint sock, const gchar *buf, gint len);
148gint fd_write_all (gint sock, const gchar *buf, gint len);
149gint fd_gets (gint sock, gchar *buf, gint len);
150gint fd_getline (gint sock, gchar **line);
151gint fd_close (gint sock);
152
153/* Functions for SSL */
154#if USE_SSL
155gint ssl_read (SSL *ssl, gchar *buf, gint len);
156gint ssl_write (SSL *ssl, const gchar *buf, gint len);
157gint ssl_write_all (SSL *ssl, const gchar *buf, gint len);
158gint ssl_gets (SSL *ssl, gchar *buf, gint len);
159gint ssl_getline (SSL *ssl, gchar **line);
160gint ssl_peek (SSL *ssl, gchar *buf, gint len);
161#endif
162
163#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2014 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include <glib.h>
25
26#ifdef G_OS_WIN32
27# include <winsock2.h>
28# include <ws2tcpip.h>
29#endif
30
31#include "socket.h"
32#include "socks.h"
33#include "utils.h"
34
35
36SocksInfo *socks_info_new(SocksType type, const gchar *proxy_host,
37 gushort proxy_port, const gchar *proxy_name,
38 const gchar *proxy_pass)
39{
40 SocksInfo *socks_info;
41
42 socks_info = g_new0(SocksInfo, 1);
43 socks_info->type = type;
44 socks_info->proxy_host = g_strdup(proxy_host);
45 socks_info->proxy_port = proxy_port;
46 socks_info->proxy_name = g_strdup(proxy_name);
47 socks_info->proxy_pass = g_strdup(proxy_pass);
48
49 return socks_info;
50}
51
52void socks_info_free(SocksInfo *socks_info)
53{
54 if (!socks_info)
55 return;
56 g_free(socks_info->proxy_host);
57 g_free(socks_info->proxy_name);
58 g_free(socks_info->proxy_pass);
59 g_free(socks_info);
60}
61
62gint socks_connect(SockInfo *sock, const gchar *hostname, gushort port,
63 SocksInfo *socks_info)
64{
65 g_return_val_if_fail(sock != NULL, -1);
66 g_return_val_if_fail(hostname != NULL, -1);
67 g_return_val_if_fail(socks_info != NULL, -1);
68
69 debug_print("socks_connect: connect to %s:%u via %s:%u\n",
70 hostname, port,
71 socks_info->proxy_host, socks_info->proxy_port);
72
73 if (socks_info->type == SOCKS_SOCKS5)
74 return socks5_connect(sock, hostname, port,
75 socks_info->proxy_name,
76 socks_info->proxy_pass);
77 else if (socks_info->type == SOCKS_SOCKS4)
78 return socks4_connect(sock, hostname, port);
79 else
80 g_warning("socks_connect: unknown SOCKS type: %d\n",
81 socks_info->type);
82
83 return -1;
84}
85
86gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port)
87{
88 guchar socks_req[1024];
89 struct hostent *hp;
90
91 g_return_val_if_fail(sock != NULL, -1);
92 g_return_val_if_fail(hostname != NULL, -1);
93
94 debug_print("socks4_connect: connect to %s:%u\n", hostname, port);
95
96 socks_req[0] = 4;
97 socks_req[1] = 1;
98 *((gushort *)(socks_req + 2)) = htons(port);
99
100 /* lookup */
101 if ((hp = my_gethostbyname(hostname)) == NULL) {
102 g_warning("socks4_connect: cannot lookup host: %s", hostname);
103 return -1;
104 }
105 if (hp->h_length != 4) {
106 g_warning("socks4_connect: invalid address length for host: %s", hostname);
107 return -1;
108 }
109 memcpy(socks_req + 4, (guchar *)hp->h_addr, 4);
110
111 /* userid (empty) */
112 socks_req[8] = 0;
113
114 if (sock_write_all(sock, (gchar *)socks_req, 9) != 9) {
115 g_warning("socks4_connect: SOCKS4 initial request write failed");
116 return -1;
117 }
118
119 if (sock_read(sock, (gchar *)socks_req, 8) != 8) {
120 g_warning("socks4_connect: SOCKS4 response read failed");
121 return -1;
122 }
123 if (socks_req[0] != 0) {
124 g_warning("socks4_connect: SOCKS4 response has invalid version");
125 return -1;
126 }
127 if (socks_req[1] != 90) {
128 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]);
129 return -1;
130 }
131
132 /* replace sock->hostname with endpoint */
133 if (sock->hostname != hostname) {
134 g_free(sock->hostname);
135 sock->hostname = g_strdup(hostname);
136 sock->port = port;
137 }
138
139 debug_print("socks4_connect: SOCKS4 connection to %s:%u successful.\n", hostname, port);
140
141 return 0;
142}
143
144gint socks5_connect(SockInfo *sock, const gchar *hostname, gushort port,
145 const gchar *proxy_name, const gchar *proxy_pass)
146{
147 guchar socks_req[1024];
148 size_t len;
149 size_t size;
150
151 g_return_val_if_fail(sock != NULL, -1);
152 g_return_val_if_fail(hostname != NULL, -1);
153
154 debug_print("socks5_connect: connect to %s:%u\n", hostname, port);
155
156 len = strlen(hostname);
157 if (len > 255) {
158 g_warning("socks5_connect: hostname too long");
159 return -1;
160 }
161
162 socks_req[0] = 5;
163 socks_req[1] = proxy_name ? 2 : 1;
164 socks_req[2] = 0;
165 socks_req[3] = 2;
166
167 if (sock_write_all(sock, (gchar *)socks_req, 2 + socks_req[1]) != 2 + socks_req[1]) {
168 g_warning("socks5_connect: SOCKS5 initial request write failed");
169 return -1;
170 }
171
172 if (sock_read(sock, (gchar *)socks_req, 2) != 2) {
173 g_warning("socks5_connect: SOCKS5 response read failed");
174 return -1;
175 }
176 if (socks_req[0] != 5) {
177 g_warning("socks5_connect: SOCKS5 response has invalid version");
178 return -1;
179 }
180 if (socks_req[1] == 2) {
181 /* auth */
182 size_t userlen, passlen;
183 gint reqlen;
184
185 if (proxy_name && proxy_pass) {
186 userlen = strlen(proxy_name);
187 passlen = strlen(proxy_pass);
188 } else
189 userlen = passlen = 0;
190
191 socks_req[0] = 1;
192 socks_req[1] = (guchar)userlen;
193 if (proxy_name && userlen > 0)
194 memcpy(socks_req + 2, proxy_name, userlen);
195 socks_req[2 + userlen] = (guchar)passlen;
196 if (proxy_pass && passlen > 0)
197 memcpy(socks_req + 2 + userlen + 1, proxy_pass, passlen);
198
199 reqlen = 2 + userlen + 1 + passlen;
200 if (sock_write_all(sock, (gchar *)socks_req, reqlen) != reqlen) {
201 g_warning("socks5_connect: SOCKS5 auth write failed");
202 return -1;
203 }
204 if (sock_read(sock, (gchar *)socks_req, 2) != 2) {
205 g_warning("socks5_connect: SOCKS5 auth response read failed");
206 return -1;
207 }
208 if (socks_req[1] != 0) {
209 g_warning("socks5_connect: SOCKS5 authentication failed: user: %s (%u %u)", proxy_name ? proxy_name : "(none)", socks_req[0], socks_req[1]);
210 return -1;
211 }
212 } else if (socks_req[1] != 0) {
213 g_warning("socks5_connect: SOCKS5 reply (%u) error", socks_req[1]);
214 return -1;
215 }
216
217 socks_req[0] = 5;
218 socks_req[1] = 1;
219 socks_req[2] = 0;
220
221 socks_req[3] = 3;
222 socks_req[4] = (guchar)len;
223 memcpy(socks_req + 5, hostname, len);
224 *((gushort *)(socks_req + 5 + len)) = htons(port);
225
226 if (sock_write_all(sock, (gchar *)socks_req, 5 + len + 2) != 5 + len + 2) {
227 g_warning("socks5_connect: SOCKS5 connect request write failed");
228 return -1;
229 }
230
231 if (sock_read(sock, (gchar *)socks_req, 10) != 10) {
232 g_warning("socks5_connect: SOCKS5 connect request response read failed");
233 return -1;
234 }
235 if (socks_req[0] != 5) {
236 g_warning("socks5_connect: SOCKS5 response has invalid version");
237 return -1;
238 }
239 if (socks_req[1] != 0) {
240 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]);
241 return -1;
242 }
243
244 size = 10;
245 if (socks_req[3] == 3)
246 size = 5 + socks_req[4] + 2;
247 else if (socks_req[3] == 4)
248 size = 4 + 16 + 2;
249 if (size > 10) {
250 size -= 10;
251 if (sock_read(sock, (gchar *)socks_req + 10, size) != size) {
252 g_warning("socks5_connect: SOCKS5 connect request response read failed");
253 return -1;
254 }
255 }
256
257 /* replace sock->hostname with endpoint */
258 if (sock->hostname != hostname) {
259 g_free(sock->hostname);
260 sock->hostname = g_strdup(hostname);
261 sock->port = port;
262 }
263
264 debug_print("socks5_connect: SOCKS5 connection to %s:%u successful.\n", hostname, port);
265
266 return 0;
267}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2010 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __SOCKS_H__
21#define __SOCKS_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28
29#include "socket.h"
30
31typedef struct _SocksInfo SocksInfo;
32
33typedef enum {
34 SOCKS_SOCKS4,
35 SOCKS_SOCKS5
36} SocksType;
37
38struct _SocksInfo
39{
40 SocksType type;
41 gchar *proxy_host;
42 gushort proxy_port;
43 gchar *proxy_name;
44 gchar *proxy_pass;
45};
46
47SocksInfo *socks_info_new(SocksType type, const gchar *proxy_host,
48 gushort proxy_port, const gchar *proxy_name,
49 const gchar *proxy_pass);
50void socks_info_free(SocksInfo *socks_info);
51
52gint socks_connect(SockInfo *sock, const gchar *hostname, gushort port,
53 SocksInfo *socks_info);
54
55gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port);
56gint socks5_connect(SockInfo *sock, const gchar *hostname, gushort port,
57 const gchar *proxy_name, const gchar *proxy_pass);
58
59#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2014 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#if USE_SSL
25
26#include "defs.h"
27
28#include <glib.h>
29#include <glib/gi18n.h>
30
31#include "utils.h"
32#include "ssl.h"
33#include "ssl_hostname_validation.h"
34
35static SSL_CTX *ssl_ctx_SSLv23 = NULL;
36static SSL_CTX *ssl_ctx_TLSv1 = NULL;
37
38static GSList *trust_list = NULL;
39static GSList *tmp_trust_list = NULL;
40static GSList *reject_list = NULL;
41
42static SSLVerifyFunc verify_ui_func = NULL;
43
44static gchar *find_certs_file(const gchar *certs_dir)
45{
46 gchar *certs_file;
47
48#define LOOK_FOR(crt) \
49{ \
50 certs_file = g_strconcat(certs_dir, G_DIR_SEPARATOR_S, crt, NULL); \
51 debug_print("looking for %s\n", certs_file); \
52 if (is_file_exist(certs_file)) \
53 return certs_file; \
54 g_free(certs_file); \
55}
56
57 if (certs_dir) {
58 LOOK_FOR("ca-certificates.crt");
59 LOOK_FOR("ca-bundle.crt");
60 LOOK_FOR("ca-root.crt");
61 LOOK_FOR("certs.crt");
62 LOOK_FOR("cert.pem");
63 }
64
65#undef LOOK_FOR
66
67 return NULL;
68}
69
70void ssl_init(void)
71{
72 gchar *certs_file, *certs_dir;
73 FILE *fp;
74
75 SSL_library_init();
76 SSL_load_error_strings();
77
78 certs_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "certs", NULL);
79 if (!is_dir_exist(certs_dir)) {
80 debug_print("ssl_init(): %s doesn't exist, or not a directory.\n",
81 certs_dir);
82 g_free(certs_dir);
83#ifdef G_OS_WIN32
84 certs_dir = g_strconcat(get_startup_dir(), G_DIR_SEPARATOR_S
85 "etc" G_DIR_SEPARATOR_S
86 "ssl" G_DIR_SEPARATOR_S "certs", NULL);
87#else
88 certs_dir = g_strdup("/etc/ssl/certs");
89#endif
90 if (!is_dir_exist(certs_dir)) {
91 debug_print("ssl_init(): %s doesn't exist, or not a directory.\n",
92 certs_dir);
93 g_free(certs_dir);
94 certs_dir = NULL;
95 }
96 }
97 if (certs_dir)
98 debug_print("ssl_init(): certs dir %s found.\n", certs_dir);
99
100 certs_file = find_certs_file(get_rc_dir());
101
102 if (certs_dir && !certs_file)
103 certs_file = find_certs_file(certs_dir);
104
105 if (!certs_file) {
106#ifdef G_OS_WIN32
107 certs_dir = g_strconcat(get_startup_dir(),
108 G_DIR_SEPARATOR_S "etc"
109 G_DIR_SEPARATOR_S "ssl", NULL);
110 certs_file = find_certs_file(certs_dir);
111 g_free(certs_dir);
112 certs_dir = NULL;
113 if (!certs_file) {
114 certs_dir = g_strconcat(get_startup_dir(),
115 G_DIR_SEPARATOR_S "etc", NULL);
116 certs_file = find_certs_file(certs_dir);
117 g_free(certs_dir);
118 certs_dir = NULL;
119 }
120#else
121 certs_file = find_certs_file("/etc/ssl");
122 if (!certs_file)
123 certs_file = find_certs_file("/etc");
124#endif
125 }
126
127 if (certs_file)
128 debug_print("ssl_init(): certs file %s found.\n", certs_file);
129
130 ssl_ctx_SSLv23 = SSL_CTX_new(SSLv23_client_method());
131 if (ssl_ctx_SSLv23 == NULL) {
132 debug_print(_("SSLv23 not available\n"));
133 } else {
134 debug_print(_("SSLv23 available\n"));
135 if ((certs_file || certs_dir) &&
136 !SSL_CTX_load_verify_locations(ssl_ctx_SSLv23, certs_file,
137 certs_dir))
138 g_warning("SSLv23 SSL_CTX_load_verify_locations failed.\n");
139 }
140
141 /* ssl_ctx_TLSv1 = SSL_CTX_new(TLSv1_client_method()); */
142 ssl_ctx_TLSv1 = SSL_CTX_new(SSLv23_client_method());
143 if (ssl_ctx_TLSv1 == NULL) {
144 debug_print(_("TLSv1 not available\n"));
145 } else {
146 debug_print(_("TLSv1 available\n"));
147 /* disable SSLv2/SSLv3 */
148 SSL_CTX_set_options(ssl_ctx_TLSv1,
149 SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
150 if ((certs_file || certs_dir) &&
151 !SSL_CTX_load_verify_locations(ssl_ctx_TLSv1, certs_file,
152 certs_dir))
153 g_warning("TLSv1 SSL_CTX_load_verify_locations failed.\n");
154 }
155
156 g_free(certs_dir);
157 g_free(certs_file);
158
159 certs_file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "trust.crt",
160 NULL);
161 if ((fp = g_fopen(certs_file, "rb")) != NULL) {
162 X509 *cert;
163
164 debug_print("ssl_init(): reading trust.crt\n");
165
166 while ((cert = PEM_read_X509(fp, NULL, NULL, NULL)) != NULL)
167 trust_list = g_slist_append(trust_list, cert);
168 fclose(fp);
169 }
170 g_free(certs_file);
171}
172
173void ssl_done(void)
174{
175 gchar *trust_file;
176 GSList *cur;
177 FILE *fp;
178
179 if (trust_list) {
180 trust_file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
181 "trust.crt", NULL);
182 if ((fp = g_fopen(trust_file, "wb")) == NULL) {
183 FILE_OP_ERROR(trust_file, "fopen");
184 }
185 for (cur = trust_list; cur != NULL; cur = cur->next) {
186 if (fp && !PEM_write_X509(fp, (X509 *)cur->data))
187 g_warning("can't write X509 to PEM file: %s",
188 trust_file);
189 X509_free((X509 *)cur->data);
190 }
191 if (fp)
192 fclose(fp);
193 g_free(trust_file);
194 g_slist_free(trust_list);
195 trust_list = NULL;
196 }
197 for (cur = tmp_trust_list; cur != NULL; cur = cur->next)
198 X509_free((X509 *)cur->data);
199 g_slist_free(tmp_trust_list);
200 tmp_trust_list = NULL;
201 for (cur = reject_list; cur != NULL; cur = cur->next)
202 X509_free((X509 *)cur->data);
203 g_slist_free(reject_list);
204 reject_list = NULL;
205
206 if (ssl_ctx_SSLv23) {
207 SSL_CTX_free(ssl_ctx_SSLv23);
208 ssl_ctx_SSLv23 = NULL;
209 }
210
211 if (ssl_ctx_TLSv1) {
212 SSL_CTX_free(ssl_ctx_TLSv1);
213 ssl_ctx_TLSv1 = NULL;
214 }
215}
216
217gboolean ssl_init_socket(SockInfo *sockinfo)
218{
219 return ssl_init_socket_with_method(sockinfo, SSL_METHOD_SSLv23);
220}
221
222static gint x509_cmp_func(gconstpointer a, gconstpointer b)
223{
224 const X509 *xa = a;
225 const X509 *xb = b;
226
227 return X509_cmp(xa, xb);
228}
229
230gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
231{
232 X509 *server_cert;
233 gint err, ret;
234
235 switch (method) {
236 case SSL_METHOD_SSLv23:
237 if (!ssl_ctx_SSLv23) {
238 g_warning(_("SSL method not available\n"));
239 return FALSE;
240 }
241 sockinfo->ssl = SSL_new(ssl_ctx_SSLv23);
242 break;
243 case SSL_METHOD_TLSv1:
244 if (!ssl_ctx_TLSv1) {
245 g_warning(_("SSL method not available\n"));
246 return FALSE;
247 }
248 sockinfo->ssl = SSL_new(ssl_ctx_TLSv1);
249 break;
250 default:
251 g_warning(_("Unknown SSL method *PROGRAM BUG*\n"));
252 return FALSE;
253 break;
254 }
255
256 if (sockinfo->ssl == NULL) {
257 g_warning(_("Error creating ssl context\n"));
258 return FALSE;
259 }
260
261 SSL_set_fd(sockinfo->ssl, sockinfo->sock);
262 while ((ret = SSL_connect(sockinfo->ssl)) != 1) {
263 err = SSL_get_error(sockinfo->ssl, ret);
264 if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
265 g_usleep(100000);
266 g_warning("SSL_connect(): try again\n");
267 continue;
268 }
269 g_warning("SSL_connect() failed with error %d, ret = %d (%s)\n",
270 err, ret, ERR_error_string(ERR_get_error(), NULL));
271 return FALSE;
272 }
273
274 /* Get the cipher */
275
276 debug_print(_("SSL connection using %s\n"),
277 SSL_get_cipher(sockinfo->ssl));
278 debug_print("SSL protocol version: %s\n",
279 SSL_get_version(sockinfo->ssl));
280
281 /* Get server's certificate (note: beware of dynamic allocation) */
282
283 if ((server_cert = SSL_get_peer_certificate(sockinfo->ssl)) != NULL) {
284 glong verify_result;
285 gboolean expired = FALSE;
286
287 if (get_debug_mode()) {
288 gchar *str;
289 guchar keyid[EVP_MAX_MD_SIZE];
290 gchar keyidstr[EVP_MAX_MD_SIZE * 3 + 1] = "";
291 guint keyidlen = 0;
292 gint i;
293
294 debug_print(_("Server certificate:\n"));
295
296 if ((str = X509_NAME_oneline(X509_get_subject_name(server_cert), 0, 0)) != NULL) {
297 debug_print(_(" Subject: %s\n"), str);
298 OPENSSL_free(str);
299 }
300
301 if ((str = X509_NAME_oneline(X509_get_issuer_name(server_cert), 0, 0)) != NULL) {
302 debug_print(_(" Issuer: %s\n"), str);
303 OPENSSL_free(str);
304 }
305 if (X509_digest(server_cert, EVP_sha1(), keyid, &keyidlen)) {
306 for (i = 0; i < keyidlen; i++)
307 g_snprintf(keyidstr + i * 3, 4, "%02x:", keyid[i]);
308 keyidstr[keyidlen * 3 - 1] = '\0';
309 debug_print(" SHA1 fingerprint: %s\n", keyidstr);
310 }
311 if (X509_digest(server_cert, EVP_md5(), keyid, &keyidlen)) {
312 for (i = 0; i < keyidlen; i++)
313 g_snprintf(keyidstr + i * 3, 4, "%02x:", keyid[i]);
314 keyidstr[keyidlen * 3 - 1] = '\0';
315 debug_print(" MD5 fingerprint: %s\n", keyidstr);
316 }
317 }
318
319 verify_result = SSL_get_verify_result(sockinfo->ssl);
320 if (verify_result == X509_V_OK) {
321 debug_print("SSL certificate verify OK\n");
322 if (ssl_validate_hostname(sockinfo->hostname, server_cert) == SSL_HOSTNAME_MATCH_FOUND) {
323 debug_print("SSL certificate hostname validation OK\n");
324 X509_free(server_cert);
325 return TRUE;
326 } else {
327 verify_result = X509_V_ERR_APPLICATION_VERIFICATION;
328 }
329 }
330
331 if (verify_result == X509_V_ERR_CERT_HAS_EXPIRED) {
332 log_message("SSL certificate of %s has expired\n", sockinfo->hostname);
333 expired = TRUE;
334 } else if (g_slist_find_custom(trust_list, server_cert,
335 x509_cmp_func) ||
336 g_slist_find_custom(tmp_trust_list, server_cert,
337 x509_cmp_func)) {
338 log_message("SSL certificate of %s previously accepted\n", sockinfo->hostname);
339 X509_free(server_cert);
340 return TRUE;
341 } else if (g_slist_find_custom(reject_list, server_cert,
342 x509_cmp_func)) {
343 log_message("SSL certificate of %s previously rejected\n", sockinfo->hostname);
344 X509_free(server_cert);
345 return FALSE;
346 }
347
348 if (verify_result == X509_V_ERR_APPLICATION_VERIFICATION) {
349 g_warning("%s: SSL hostname validation failed\n",
350 sockinfo->hostname);
351 } else {
352 g_warning("%s: SSL certificate verify failed (%ld: %s)\n",
353 sockinfo->hostname, verify_result,
354 X509_verify_cert_error_string(verify_result));
355 }
356
357 if (verify_ui_func) {
358 gint res;
359
360 res = verify_ui_func(sockinfo, sockinfo->hostname,
361 server_cert, verify_result);
362 /* 0: accept 1: temporarily accept -1: reject */
363 if (res < 0) {
364 debug_print("SSL certificate of %s rejected\n",
365 sockinfo->hostname);
366#if 0
367 reject_list = g_slist_prepend
368 (reject_list, X509_dup(server_cert));
369#endif
370 X509_free(server_cert);
371 return FALSE;
372 } else if (res > 0) {
373 debug_print("Temporarily accept SSL certificate of %s\n", sockinfo->hostname);
374 if (!expired)
375 tmp_trust_list = g_slist_prepend(tmp_trust_list, X509_dup(server_cert));
376 } else {
377 debug_print("Permanently accept SSL certificate of %s\n", sockinfo->hostname);
378 if (!expired)
379 trust_list = g_slist_prepend(trust_list, X509_dup(server_cert));
380 }
381 }
382
383 X509_free(server_cert);
384 } else {
385 g_warning("%s: couldn't get SSL certificate\n",
386 sockinfo->hostname);
387 return FALSE;
388 }
389
390 return TRUE;
391}
392
393void ssl_done_socket(SockInfo *sockinfo)
394{
395 if (sockinfo->ssl) {
396 SSL_free(sockinfo->ssl);
397 }
398}
399
400void ssl_set_verify_func(SSLVerifyFunc func)
401{
402 verify_ui_func = func;
403}
404
405#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2006 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __SSL_H__
21#define __SSL_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#if USE_SSL
28
29#include <glib.h>
30#include <openssl/crypto.h>
31#include <openssl/x509.h>
32#include <openssl/pem.h>
33#include <openssl/ssl.h>
34#include <openssl/err.h>
35
36#include "socket.h"
37
38typedef enum {
39 SSL_METHOD_SSLv23,
40 SSL_METHOD_TLSv1
41} SSLMethod;
42
43typedef enum {
44 SSL_NONE,
45 SSL_TUNNEL,
46 SSL_STARTTLS
47} SSLType;
48
49typedef gint (*SSLVerifyFunc) (SockInfo *sockinfo,
50 const gchar *hostname,
51 X509 *server_cert,
52 glong verify_result);
53
54void ssl_init (void);
55void ssl_done (void);
56gboolean ssl_init_socket (SockInfo *sockinfo);
57gboolean ssl_init_socket_with_method (SockInfo *sockinfo,
58 SSLMethod method);
59void ssl_done_socket (SockInfo *sockinfo);
60
61void ssl_set_verify_func (SSLVerifyFunc func);
62
63#endif /* USE_SSL */
64
65#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 @@
1/*
2 * Helper functions to perform basic hostname validation using OpenSSL.
3 *
4 * Copyright (C) 2012, iSEC Partners.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy of
7 * this software and associated documentation files (the "Software"), to deal in
8 * the Software without restriction, including without limitation the rights to
9 + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10 * of the Software, and to permit persons to whom the Software is furnished to do
11 + so, subject to the following conditions:
12 +
13 + The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 *
24 * Author: Alban Diquet
25 *
26 * https://github.com/iSECPartners/ssl-conservatory
27 *
28 * Modified naming convention to match LibSylph.
29 *
30 */
31
32#ifdef HAVE_CONFIG_H
33# include "config.h"
34#endif
35
36#if USE_SSL
37
38#include <glib.h>
39
40#include <strings.h>
41#include <openssl/x509v3.h>
42#include <openssl/ssl.h>
43
44#include "utils.h"
45#include "ssl_hostname_validation.h"
46
47
48#define HOSTNAME_MAX_SIZE 255
49
50
51/* The following host_match() function is based on cURL/libcurl code. */
52
53/***************************************************************************
54 * _ _ ____ _
55 * Project ___| | | | _ \| |
56 * / __| | | | |_) | |
57 * | (__| |_| | _ <| |___
58 * \___|\___/|_| \_\_____|
59 *
60 * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
61 *
62 * This software is licensed as described in the file COPYING, which
63 * you should have received as part of this distribution. The terms
64 * are also available at http://curl.haxx.se/docs/copyright.html.
65 *
66 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
67 * copies of the Software, and permit persons to whom the Software is
68 * furnished to do so, under the terms of the COPYING file.
69 *
70 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
71 * KIND, either express or implied.
72 *
73 ***************************************************************************/
74
75/***************************************************************************
76 *
77 * COPYRIGHT AND PERMISSION NOTICE
78 *
79 * Copyright (c) 1996 - 2014, Daniel Stenberg, <daniel@haxx.se>.
80 *
81 * All rights reserved.
82 *
83 * Permission to use, copy, modify, and distribute this software for any purpose
84 * with or without fee is hereby granted, provided that the above copyright
85 * notice and this permission notice appear in all copies.
86 *
87 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
88 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
89 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
90 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
91 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
92 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
93 * OR OTHER DEALINGS IN THE SOFTWARE.
94 *
95 * Except as contained in this notice, the name of a copyright holder shall not
96 * be used in advertising or otherwise to promote the sale, use or other dealings
97 * in this Software without prior written authorization of the copyright holder.
98 *
99 ***************************************************************************/
100
101/**
102* Match a hostname against a wildcard pattern.
103* E.g.
104* "foo.host.com" matches "*.host.com".
105*
106* We use the matching rule described in RFC6125, section 6.4.3.
107* http://tools.ietf.org/html/rfc6125#section-6.4.3
108*/
109
110static int host_match(const char *hostname, const char *pattern)
111{
112 const char *pattern_label_end, *pattern_wildcard, *hostname_label_end;
113 size_t prefixlen, suffixlen;
114
115 if (!pattern || !*pattern || !hostname || !*hostname)
116 return SSL_HOSTNAME_MATCH_NOT_FOUND;
117
118 /* trivial case */
119 if (g_ascii_strcasecmp(pattern, hostname) == 0)
120 return SSL_HOSTNAME_MATCH_FOUND;
121
122 pattern_wildcard = strchr(pattern, '*');
123 if (pattern_wildcard == NULL) {
124 return g_ascii_strcasecmp(pattern, hostname) == 0 ?
125 SSL_HOSTNAME_MATCH_FOUND : SSL_HOSTNAME_MATCH_NOT_FOUND;
126 }
127
128 /* We require at least 2 dots in pattern to avoid too wide wildcard
129 match. */
130 pattern_label_end = strchr(pattern, '.');
131 if (pattern_label_end == NULL ||
132 strchr(pattern_label_end + 1, '.') == NULL ||
133 pattern_wildcard > pattern_label_end ||
134 g_ascii_strncasecmp(pattern, "xn--", 4) == 0) {
135 return g_ascii_strcasecmp(pattern, hostname) == 0 ?
136 SSL_HOSTNAME_MATCH_FOUND : SSL_HOSTNAME_MATCH_NOT_FOUND;
137 }
138
139 hostname_label_end = strchr(hostname, '.');
140 if (hostname_label_end == NULL ||
141 g_ascii_strcasecmp(pattern_label_end, hostname_label_end) != 0)
142 return SSL_HOSTNAME_MATCH_NOT_FOUND;
143
144 /* The wildcard must match at least one character, so the left-most
145 label of the hostname is at least as large as the left-most label
146 of the pattern. */
147 if (hostname_label_end - hostname < pattern_label_end - pattern)
148 return SSL_HOSTNAME_MATCH_NOT_FOUND;
149
150 prefixlen = pattern_wildcard - pattern;
151 suffixlen = pattern_label_end - (pattern_wildcard + 1);
152 return g_ascii_strncasecmp(pattern, hostname, prefixlen) == 0 &&
153 g_ascii_strncasecmp(pattern_wildcard + 1, hostname_label_end - suffixlen, suffixlen) == 0 ?
154 SSL_HOSTNAME_MATCH_FOUND : SSL_HOSTNAME_MATCH_NOT_FOUND;
155}
156
157
158/**
159* Tries to find a match for hostname in the certificate's Common Name field.
160*
161* Returns MatchFound if a match was found.
162* Returns MatchNotFound if no matches were found.
163* Returns MalformedCertificate if the Common Name had a NUL character embedded in it.
164* Returns Error if the Common Name could not be extracted.
165*/
166static SSLHostnameValidationResult matches_common_name(const char *hostname, const X509 *server_cert) {
167 int common_name_loc = -1;
168 X509_NAME_ENTRY *common_name_entry = NULL;
169 ASN1_STRING *common_name_asn1 = NULL;
170 char *common_name_str = NULL;
171
172 // Find the position of the CN field in the Subject field of the certificate
173 common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) server_cert), NID_commonName, -1);
174 if (common_name_loc < 0) {
175 return SSL_HOSTNAME_ERROR;
176 }
177
178 // Extract the CN field
179 common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *) server_cert), common_name_loc);
180 if (common_name_entry == NULL) {
181 return SSL_HOSTNAME_ERROR;
182 }
183
184 // Convert the CN field to a C string
185 common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
186 if (common_name_asn1 == NULL) {
187 return SSL_HOSTNAME_ERROR;
188 }
189 common_name_str = (char *) ASN1_STRING_data(common_name_asn1);
190
191 debug_print("matches_common_name: %s\n", common_name_str);
192
193 // Make sure there isn't an embedded NUL character in the CN
194 if (ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) {
195 return SSL_HOSTNAME_MALFORMED_CERTIFICATE;
196 }
197
198 // Compare expected hostname with the CN
199 return host_match(hostname, common_name_str);
200}
201
202
203/**
204* Tries to find a match for hostname in the certificate's Subject Alternative Name extension.
205*
206* Returns MatchFound if a match was found.
207* Returns MatchNotFound if no matches were found.
208* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
209* Returns NoSANPresent if the SAN extension was not present in the certificate.
210*/
211static SSLHostnameValidationResult matches_subject_alternative_name(const char *hostname, const X509 *server_cert) {
212 SSLHostnameValidationResult result = SSL_HOSTNAME_MATCH_NOT_FOUND;
213 int i;
214 int san_names_nb = -1;
215 STACK_OF(GENERAL_NAME) *san_names = NULL;
216
217 // Try to extract the names within the SAN extension from the certificate
218 san_names = X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL);
219 if (san_names == NULL) {
220 return SSL_HOSTNAME_NO_SAN_PRESENT;
221 }
222 san_names_nb = sk_GENERAL_NAME_num(san_names);
223
224 // Check each name within the extension
225 for (i=0; i<san_names_nb; i++) {
226 const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
227
228 if (current_name->type == GEN_DNS) {
229 // Current name is a DNS name, let's check it
230 char *dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName);
231
232 debug_print("matches_subject_alternative_name: %s\n", dns_name);
233
234 // Make sure there isn't an embedded NUL character in the DNS name
235 if (ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) {
236 result = SSL_HOSTNAME_MALFORMED_CERTIFICATE;
237 break;
238 }
239 else { // Compare expected hostname with the DNS name
240 if (host_match(hostname, dns_name) == SSL_HOSTNAME_MATCH_FOUND) {
241 result = SSL_HOSTNAME_MATCH_FOUND;
242 break;
243 }
244 }
245 }
246 }
247 sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
248
249 return result;
250}
251
252
253/**
254* Validates the server's identity by looking for the expected hostname in the
255* server's certificate. As described in RFC 6125, it first tries to find a match
256* in the Subject Alternative Name extension. If the extension is not present in
257* the certificate, it checks the Common Name instead.
258*
259* Returns MatchFound if a match was found.
260* Returns MatchNotFound if no matches were found.
261* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
262* Returns Error if there was an error.
263*/
264SSLHostnameValidationResult ssl_validate_hostname(const char *hostname, const X509 *server_cert) {
265 SSLHostnameValidationResult result;
266
267 debug_print("ssl_validate_hostname: validating hostname: %s\n", hostname);
268
269 if((hostname == NULL) || (server_cert == NULL))
270 return SSL_HOSTNAME_ERROR;
271
272 // First try the Subject Alternative Names extension
273 result = matches_subject_alternative_name(hostname, server_cert);
274 if (result == SSL_HOSTNAME_NO_SAN_PRESENT) {
275 // Extension was not found: try the Common Name
276 result = matches_common_name(hostname, server_cert);
277 }
278
279 return result;
280}
281
282#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 @@
1/*
2 * Helper functions to perform basic hostname validation using OpenSSL.
3 *
4 * Copyright (C) 2012, iSEC Partners.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy of
7 * this software and associated documentation files (the "Software"), to deal in
8 * the Software without restriction, including without limitation the rights to
9 + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10 * of the Software, and to permit persons to whom the Software is furnished to do
11 + so, subject to the following conditions:
12 +
13 + The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 *
24 * Author: Alban Diquet
25 *
26 * https://github.com/iSECPartners/ssl-conservatory
27 *
28 * Modified naming convention to match LibSylph.
29 *
30 */
31
32#ifndef __SSL_HOSTNAME_VALIDATION_H__
33#define __SSL_HOSTNAME_VALIDATION_H__
34
35#ifdef HAVE_CONFIG_H
36# include "config.h"
37#endif
38
39#if USE_SSL
40
41#include <openssl/x509.h>
42
43typedef enum {
44 SSL_HOSTNAME_MATCH_FOUND,
45 SSL_HOSTNAME_MATCH_NOT_FOUND,
46 SSL_HOSTNAME_NO_SAN_PRESENT,
47 SSL_HOSTNAME_MALFORMED_CERTIFICATE,
48 SSL_HOSTNAME_ERROR
49} SSLHostnameValidationResult;
50
51/**
52* Validates the server's identity by looking for the expected hostname in the
53* server's certificate. As described in RFC 6125, it first tries to find a match
54* in the Subject Alternative Name extension. If the extension is not present in
55* the certificate, it checks the Common Name instead.
56*
57* Returns MatchFound if a match was found.
58* Returns MatchNotFound if no matches were found.
59* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
60* Returns Error if there was an error.
61*/
62SSLHostnameValidationResult ssl_validate_hostname(const char *hostname, const X509 *server_cert);
63
64#endif /* USE_SSL */
65
66#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include <glib.h>
21#include <string.h>
22
23#include "stringtable.h"
24#include "utils.h"
25
26/* alfons - hashed string table (I wasn't content with GStringChunk;
27 * can't recall why :-) */
28
29#if 0
30#define XXX_DEBUG \
31 debug_print
32#else
33#define XXX_DEBUG \
34 if (0) debug_print
35#endif
36
37typedef struct StringEntry_ {
38 gint ref_count;
39 gchar *string;
40} StringEntry;
41
42static StringEntry *string_entry_new(const gchar *str)
43{
44 StringEntry *entry;
45
46 entry = g_new0(StringEntry, 1);
47 entry->ref_count = 1;
48 entry->string = g_strdup(str);
49 return entry;
50}
51
52static void string_entry_free(StringEntry *entry)
53{
54 g_return_if_fail(entry != NULL);
55
56 g_free(entry->string);
57 g_free(entry);
58}
59
60StringTable *string_table_new(void)
61{
62 StringTable *strtable;
63
64 strtable = g_new0(StringTable, 1);
65 g_return_val_if_fail(strtable != NULL, NULL);
66 strtable->hash_table = g_hash_table_new(g_str_hash, g_str_equal);
67 g_return_val_if_fail(strtable->hash_table, NULL);
68 return strtable;
69}
70
71gchar *string_table_lookup_string(StringTable *table, const gchar *str)
72{
73 StringEntry *entry;
74
75 entry = g_hash_table_lookup(table->hash_table, str);
76
77 if (entry) {
78 return entry->string;
79 } else {
80 return NULL;
81 }
82}
83
84gchar *string_table_insert_string(StringTable *table, const gchar *str)
85{
86 StringEntry *entry;
87
88 entry = g_hash_table_lookup(table->hash_table, str);
89
90 if (entry) {
91 entry->ref_count++;
92 XXX_DEBUG ("ref++ for %s (%d)\n", entry->string,
93 entry->ref_count);
94 } else {
95 entry = string_entry_new(str);
96 XXX_DEBUG ("inserting %s\n", str);
97 /* insert entry->string instead of str, since it can be
98 * invalid pointer after this. */
99 g_hash_table_insert(table->hash_table, entry->string, entry);
100 }
101
102 return entry->string;
103}
104
105void string_table_free_string(StringTable *table, const gchar *str)
106{
107 StringEntry *entry;
108
109 entry = g_hash_table_lookup(table->hash_table, str);
110
111 if (entry) {
112 entry->ref_count--;
113 if (entry->ref_count <= 0) {
114 XXX_DEBUG ("refcount of string %s dropped to zero\n",
115 entry->string);
116 g_hash_table_remove(table->hash_table, str);
117 string_entry_free(entry);
118 } else {
119 XXX_DEBUG ("ref-- for %s (%d)\n", entry->string,
120 entry->ref_count);
121 }
122 }
123}
124
125static gboolean string_table_remove_for_each_fn(gchar *key, StringEntry *entry,
126 gpointer user_data)
127{
128 g_return_val_if_fail(key != NULL, TRUE);
129 g_return_val_if_fail(entry != NULL, TRUE);
130
131 string_entry_free(entry);
132
133 return TRUE;
134}
135
136void string_table_free(StringTable *table)
137{
138 g_return_if_fail(table != NULL);
139 g_return_if_fail(table->hash_table != NULL);
140
141 g_hash_table_foreach_remove(table->hash_table,
142 (GHRFunc)string_table_remove_for_each_fn,
143 NULL);
144 g_hash_table_destroy(table->hash_table);
145 g_free(table);
146}
147
148static void string_table_stats_for_each_fn(gchar *key, StringEntry *entry,
149 guint *totals)
150{
151 if (entry->ref_count > 1) {
152 *totals += strlen(key) * (entry->ref_count - 1);
153 }
154}
155
156void string_table_get_stats(StringTable *table)
157{
158 guint totals = 0;
159
160 g_hash_table_foreach(table->hash_table,
161 (GHFunc)string_table_stats_for_each_fn, &totals);
162 XXX_DEBUG ("TOTAL UNSPILLED %d (%dK)\n", totals, totals / 1024);
163}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef STRINGTABLE_H__
21#define STRINGTABLE_H__
22
23#include <glib.h>
24
25typedef struct {
26 GHashTable *hash_table;
27} StringTable;
28
29StringTable *string_table_new (void);
30void string_table_free (StringTable *table);
31
32gchar *string_table_lookup_string (StringTable *table, const gchar *str);
33gchar *string_table_insert_string (StringTable *table, const gchar *str);
34void string_table_free_string (StringTable *table, const gchar *str);
35
36void string_table_get_stats (StringTable *table);
37
38#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 @@
1
2#include <glib-object.h>
3
4
5#ifdef G_ENABLE_DEBUG
6#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v)
7#define g_marshal_value_peek_char(v) g_value_get_char (v)
8#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v)
9#define g_marshal_value_peek_int(v) g_value_get_int (v)
10#define g_marshal_value_peek_uint(v) g_value_get_uint (v)
11#define g_marshal_value_peek_long(v) g_value_get_long (v)
12#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v)
13#define g_marshal_value_peek_int64(v) g_value_get_int64 (v)
14#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v)
15#define g_marshal_value_peek_enum(v) g_value_get_enum (v)
16#define g_marshal_value_peek_flags(v) g_value_get_flags (v)
17#define g_marshal_value_peek_float(v) g_value_get_float (v)
18#define g_marshal_value_peek_double(v) g_value_get_double (v)
19#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v)
20#define g_marshal_value_peek_param(v) g_value_get_param (v)
21#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v)
22#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v)
23#define g_marshal_value_peek_object(v) g_value_get_object (v)
24#else /* !G_ENABLE_DEBUG */
25/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
26 * Do not access GValues directly in your code. Instead, use the
27 * g_value_get_*() functions
28 */
29#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int
30#define g_marshal_value_peek_char(v) (v)->data[0].v_int
31#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint
32#define g_marshal_value_peek_int(v) (v)->data[0].v_int
33#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint
34#define g_marshal_value_peek_long(v) (v)->data[0].v_long
35#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong
36#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64
37#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64
38#define g_marshal_value_peek_enum(v) (v)->data[0].v_long
39#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong
40#define g_marshal_value_peek_float(v) (v)->data[0].v_float
41#define g_marshal_value_peek_double(v) (v)->data[0].v_double
42#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer
43#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer
44#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer
45#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer
46#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer
47#endif /* !G_ENABLE_DEBUG */
48
49
50/* VOID:VOID (syl-marshal.list:1) */
51
52/* VOID:POINTER,STRING,UINT (syl-marshal.list:2) */
53void
54syl_marshal_VOID__POINTER_STRING_UINT (GClosure *closure,
55 GValue *return_value G_GNUC_UNUSED,
56 guint n_param_values,
57 const GValue *param_values,
58 gpointer invocation_hint G_GNUC_UNUSED,
59 gpointer marshal_data)
60{
61 typedef void (*GMarshalFunc_VOID__POINTER_STRING_UINT) (gpointer data1,
62 gpointer arg_1,
63 gpointer arg_2,
64 guint arg_3,
65 gpointer data2);
66 register GMarshalFunc_VOID__POINTER_STRING_UINT callback;
67 register GCClosure *cc = (GCClosure*) closure;
68 register gpointer data1, data2;
69
70 g_return_if_fail (n_param_values == 4);
71
72 if (G_CCLOSURE_SWAP_DATA (closure))
73 {
74 data1 = closure->data;
75 data2 = g_value_peek_pointer (param_values + 0);
76 }
77 else
78 {
79 data1 = g_value_peek_pointer (param_values + 0);
80 data2 = closure->data;
81 }
82 callback = (GMarshalFunc_VOID__POINTER_STRING_UINT) (marshal_data ? marshal_data : cc->callback);
83
84 callback (data1,
85 g_marshal_value_peek_pointer (param_values + 1),
86 g_marshal_value_peek_string (param_values + 2),
87 g_marshal_value_peek_uint (param_values + 3),
88 data2);
89}
90
91/* VOID:POINTER (syl-marshal.list:3) */
92
93/* VOID:POINTER,STRING,STRING (syl-marshal.list:4) */
94void
95syl_marshal_VOID__POINTER_STRING_STRING (GClosure *closure,
96 GValue *return_value G_GNUC_UNUSED,
97 guint n_param_values,
98 const GValue *param_values,
99 gpointer invocation_hint G_GNUC_UNUSED,
100 gpointer marshal_data)
101{
102 typedef void (*GMarshalFunc_VOID__POINTER_STRING_STRING) (gpointer data1,
103 gpointer arg_1,
104 gpointer arg_2,
105 gpointer arg_3,
106 gpointer data2);
107 register GMarshalFunc_VOID__POINTER_STRING_STRING callback;
108 register GCClosure *cc = (GCClosure*) closure;
109 register gpointer data1, data2;
110
111 g_return_if_fail (n_param_values == 4);
112
113 if (G_CCLOSURE_SWAP_DATA (closure))
114 {
115 data1 = closure->data;
116 data2 = g_value_peek_pointer (param_values + 0);
117 }
118 else
119 {
120 data1 = g_value_peek_pointer (param_values + 0);
121 data2 = closure->data;
122 }
123 callback = (GMarshalFunc_VOID__POINTER_STRING_STRING) (marshal_data ? marshal_data : cc->callback);
124
125 callback (data1,
126 g_marshal_value_peek_pointer (param_values + 1),
127 g_marshal_value_peek_string (param_values + 2),
128 g_marshal_value_peek_string (param_values + 3),
129 data2);
130}
131
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 @@
1
2#ifndef __syl_marshal_MARSHAL_H__
3#define __syl_marshal_MARSHAL_H__
4
5#include <glib-object.h>
6
7G_BEGIN_DECLS
8
9/* VOID:VOID (syl-marshal.list:1) */
10#define syl_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID
11
12/* VOID:POINTER,STRING,UINT (syl-marshal.list:2) */
13extern void syl_marshal_VOID__POINTER_STRING_UINT (GClosure *closure,
14 GValue *return_value,
15 guint n_param_values,
16 const GValue *param_values,
17 gpointer invocation_hint,
18 gpointer marshal_data);
19
20/* VOID:POINTER (syl-marshal.list:3) */
21#define syl_marshal_VOID__POINTER g_cclosure_marshal_VOID__POINTER
22
23/* VOID:POINTER,STRING,STRING (syl-marshal.list:4) */
24extern void syl_marshal_VOID__POINTER_STRING_STRING (GClosure *closure,
25 GValue *return_value,
26 guint n_param_values,
27 const GValue *param_values,
28 gpointer invocation_hint,
29 gpointer marshal_data);
30
31G_END_DECLS
32
33#endif /* __syl_marshal_MARSHAL_H__ */
34
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 @@
1VOID:VOID
2VOID:POINTER,STRING,UINT
3VOID:POINTER
4VOID: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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2010 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28
29#ifdef G_OS_UNIX
30# include <signal.h>
31#endif
32
33#if HAVE_LOCALE_H
34# include <locale.h>
35#endif
36
37#include "sylmain.h"
38#include "syl-marshal.h"
39#include "prefs_common.h"
40#include "account.h"
41#include "filter.h"
42#include "folder.h"
43#include "socket.h"
44#include "codeconv.h"
45#include "utils.h"
46
47#if USE_SSL
48# include "ssl.h"
49#endif
50
51#ifndef PACKAGE
52# define PACKAGE GETTEXT_PACKAGE
53#endif
54
55G_DEFINE_TYPE(SylApp, syl_app, G_TYPE_OBJECT);
56
57enum {
58 INIT_DONE,
59 APP_EXIT,
60 APP_FORCE_EXIT,
61 ADD_MSG,
62 REMOVE_MSG,
63 REMOVE_ALL_MSG,
64 REMOVE_FOLDER,
65 MOVE_FOLDER,
66 FOLDERLIST_UPDATED,
67 ACCOUNT_UPDATED,
68 LAST_SIGNAL
69};
70
71static guint app_signals[LAST_SIGNAL] = { 0 };
72
73static GObject *app = NULL;
74
75
76static void syl_app_init(SylApp *self)
77{
78}
79
80static void syl_app_class_init(SylAppClass *klass)
81{
82 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
83
84 app_signals[INIT_DONE] =
85 g_signal_new("init-done",
86 G_TYPE_FROM_CLASS(gobject_class),
87 G_SIGNAL_RUN_FIRST,
88 0,
89 NULL, NULL,
90 syl_marshal_VOID__VOID,
91 G_TYPE_NONE,
92 0);
93 app_signals[APP_EXIT] =
94 g_signal_new("app-exit",
95 G_TYPE_FROM_CLASS(gobject_class),
96 G_SIGNAL_RUN_FIRST,
97 0,
98 NULL, NULL,
99 syl_marshal_VOID__VOID,
100 G_TYPE_NONE,
101 0);
102 app_signals[APP_FORCE_EXIT] =
103 g_signal_new("app-force-exit",
104 G_TYPE_FROM_CLASS(gobject_class),
105 G_SIGNAL_RUN_FIRST,
106 0,
107 NULL, NULL,
108 syl_marshal_VOID__VOID,
109 G_TYPE_NONE,
110 0);
111 app_signals[ADD_MSG] =
112 g_signal_new("add-msg",
113 G_TYPE_FROM_CLASS(gobject_class),
114 G_SIGNAL_RUN_FIRST,
115 0,
116 NULL, NULL,
117 syl_marshal_VOID__POINTER_STRING_UINT,
118 G_TYPE_NONE,
119 3,
120 G_TYPE_POINTER,
121 G_TYPE_STRING,
122 G_TYPE_UINT);
123 app_signals[REMOVE_MSG] =
124 g_signal_new("remove-msg",
125 G_TYPE_FROM_CLASS(gobject_class),
126 G_SIGNAL_RUN_FIRST,
127 0,
128 NULL, NULL,
129 syl_marshal_VOID__POINTER_STRING_UINT,
130 G_TYPE_NONE,
131 3,
132 G_TYPE_POINTER,
133 G_TYPE_STRING,
134 G_TYPE_UINT);
135 app_signals[REMOVE_ALL_MSG] =
136 g_signal_new("remove-all-msg",
137 G_TYPE_FROM_CLASS(gobject_class),
138 G_SIGNAL_RUN_FIRST,
139 0,
140 NULL, NULL,
141 syl_marshal_VOID__POINTER,
142 G_TYPE_NONE,
143 1,
144 G_TYPE_POINTER);
145 app_signals[REMOVE_FOLDER] =
146 g_signal_new("remove-folder",
147 G_TYPE_FROM_CLASS(gobject_class),
148 G_SIGNAL_RUN_FIRST,
149 0,
150 NULL, NULL,
151 syl_marshal_VOID__POINTER,
152 G_TYPE_NONE,
153 1,
154 G_TYPE_POINTER);
155 app_signals[MOVE_FOLDER] =
156 g_signal_new("move-folder",
157 G_TYPE_FROM_CLASS(gobject_class),
158 G_SIGNAL_RUN_FIRST,
159 0,
160 NULL, NULL,
161 syl_marshal_VOID__POINTER_STRING_STRING,
162 G_TYPE_NONE,
163 3,
164 G_TYPE_POINTER,
165 G_TYPE_STRING,
166 G_TYPE_STRING);
167 app_signals[FOLDERLIST_UPDATED] =
168 g_signal_new("folderlist-updated",
169 G_TYPE_FROM_CLASS(gobject_class),
170 G_SIGNAL_RUN_FIRST,
171 0,
172 NULL, NULL,
173 syl_marshal_VOID__VOID,
174 G_TYPE_NONE,
175 0);
176 app_signals[ACCOUNT_UPDATED] =
177 g_signal_new("account-updated",
178 G_TYPE_FROM_CLASS(gobject_class),
179 G_SIGNAL_RUN_FIRST,
180 0,
181 NULL, NULL,
182 syl_marshal_VOID__VOID,
183 G_TYPE_NONE,
184 0);
185}
186
187GObject *syl_app_create(void)
188{
189 if (!app)
190 app = g_object_new(SYL_TYPE_APP, NULL);
191 return app;
192}
193
194GObject *syl_app_get(void)
195{
196 return app;
197}
198
199void syl_init(void)
200{
201#ifdef G_OS_WIN32
202 gchar *newpath;
203 const gchar *lang_env;
204
205 /* disable locale variable such as "LANG=1041" */
206
207#define DISABLE_DIGIT_LOCALE(envstr) \
208{ \
209 lang_env = g_getenv(envstr); \
210 if (lang_env && g_ascii_isdigit(lang_env[0])) \
211 g_unsetenv(envstr); \
212}
213
214 DISABLE_DIGIT_LOCALE("LC_ALL");
215 DISABLE_DIGIT_LOCALE("LANG");
216 DISABLE_DIGIT_LOCALE("LC_CTYPE");
217 DISABLE_DIGIT_LOCALE("LC_MESSAGES");
218
219#undef DISABLE_DIGIT_LOCALE
220
221 g_unsetenv("LANGUAGE");
222#endif /* G_OS_WIN32 */
223
224#ifdef HAVE_LOCALE_H
225 setlocale(LC_ALL, "");
226#endif
227
228 set_startup_dir();
229
230#ifdef G_OS_WIN32
231 /* include startup directory into %PATH% for GSpawn */
232 newpath = g_strconcat(get_startup_dir(), ";", g_getenv("PATH"), NULL);
233 g_setenv("PATH", newpath, TRUE);
234 g_free(newpath);
235#endif
236
237#ifdef ENABLE_NLS
238 syl_init_gettext(PACKAGE, LOCALEDIR);
239 textdomain(PACKAGE);
240#endif
241
242 sock_init();
243
244#ifdef G_OS_UNIX
245 /* ignore SIGPIPE signal for preventing sudden death of program */
246 signal(SIGPIPE, SIG_IGN);
247#endif
248}
249
250#define MAKE_DIR_IF_NOT_EXIST(dir) \
251{ \
252 if (!is_dir_exist(dir)) { \
253 if (is_file_exist(dir)) { \
254 g_warning("File '%s' already exists. " \
255 "Can't create folder.", dir); \
256 return -1; \
257 } \
258 if (make_dir(dir) < 0) \
259 return -1; \
260 } \
261}
262
263void syl_init_gettext(const gchar *package, const gchar *dirname)
264{
265#ifdef ENABLE_NLS
266 if (g_path_is_absolute(dirname))
267 bindtextdomain(package, dirname);
268 else {
269 gchar *locale_dir;
270
271 locale_dir = g_strconcat(get_startup_dir(), G_DIR_SEPARATOR_S,
272 dirname, NULL);
273#ifdef G_OS_WIN32
274 {
275 gchar *locale_dir_;
276
277 locale_dir_ = g_locale_from_utf8(locale_dir, -1,
278 NULL, NULL, NULL);
279 if (locale_dir_) {
280 g_free(locale_dir);
281 locale_dir = locale_dir_;
282 }
283 }
284#endif /* G_OS_WIN32 */
285 bindtextdomain(package, locale_dir);
286 g_free(locale_dir);
287 }
288
289 bind_textdomain_codeset(package, CS_UTF_8);
290#endif /* ENABLE_NLS */
291}
292
293gint syl_setup_rc_dir(void)
294{
295 if (!is_dir_exist(get_rc_dir())) {
296 if (make_dir_hier(get_rc_dir()) < 0)
297 return -1;
298 }
299
300 MAKE_DIR_IF_NOT_EXIST(get_mail_base_dir());
301
302 CHDIR_RETURN_VAL_IF_FAIL(get_rc_dir(), -1);
303
304 MAKE_DIR_IF_NOT_EXIST(get_imap_cache_dir());
305 MAKE_DIR_IF_NOT_EXIST(get_news_cache_dir());
306 MAKE_DIR_IF_NOT_EXIST(get_mime_tmp_dir());
307 MAKE_DIR_IF_NOT_EXIST(get_tmp_dir());
308 MAKE_DIR_IF_NOT_EXIST(UIDL_DIR);
309 MAKE_DIR_IF_NOT_EXIST(PLUGIN_DIR);
310
311 /* remove temporary files */
312 remove_all_files(get_tmp_dir());
313 remove_all_files(get_mime_tmp_dir());
314
315 return 0;
316}
317
318void syl_save_all_state(void)
319{
320 folder_write_list();
321 prefs_common_write_config();
322 filter_write_config();
323 account_write_config_all();
324}
325
326void syl_cleanup(void)
327{
328 /* remove temporary files */
329 remove_all_files(get_tmp_dir());
330 remove_all_files(get_mime_tmp_dir());
331#if GLIB_CHECK_VERSION(2, 6, 0)
332 g_log_set_default_handler(g_log_default_handler, NULL);
333#endif
334 close_log_file();
335
336 sock_cleanup();
337
338 if (app) {
339 g_object_unref(app);
340 app = NULL;
341 }
342}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2009 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __SYLMAIN_H__
21#define __SYLMAIN_H__
22
23#include <glib.h>
24#include <glib-object.h>
25
26/* SylApp object */
27
28#define SYL_TYPE_APP (syl_app_get_type())
29#define SYL_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SYL_TYPE_APP, SylApp))
30#define SYL_IS_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SYL_TYPE_APP))
31#define SYL_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SYL_TYPE_APP, SylAppClass))
32#define SYL_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SYL_TYPE_APP))
33#define SYL_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SYL_TYPE_APP, SylAppClass))
34
35typedef struct _SylApp SylApp;
36typedef struct _SylAppClass SylAppClass;
37
38struct _SylApp
39{
40 GObject parent_instance;
41};
42
43struct _SylAppClass
44{
45 GObjectClass parent_class;
46};
47
48GObject *syl_app_create (void);
49GObject *syl_app_get (void);
50
51void syl_init (void);
52void syl_init_gettext (const gchar *package, const gchar *dirname);
53gint syl_setup_rc_dir (void);
54void syl_save_all_state (void);
55void syl_cleanup (void);
56
57#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include <glib.h>
25#include <string.h>
26#include <ctype.h>
27
28#include "codeconv.h"
29#include "base64.h"
30#include "quoted-printable.h"
31
32#define ENCODED_WORD_BEGIN "=?"
33#define ENCODED_WORD_END "?="
34
35/* Decodes headers based on RFC2045 and RFC2047. */
36
37gchar *unmime_header(const gchar *encoded_str)
38{
39 const gchar *p = encoded_str;
40 const gchar *eword_begin_p, *encoding_begin_p, *text_begin_p,
41 *eword_end_p;
42 gchar charset[32];
43 gchar encoding;
44 gchar *conv_str;
45 GString *outbuf;
46 gchar *out_str;
47 gsize out_len;
48
49 outbuf = g_string_sized_new(strlen(encoded_str) * 2);
50
51 while (*p != '\0') {
52 gchar *decoded_text = NULL;
53 gint len;
54
55 eword_begin_p = strstr(p, ENCODED_WORD_BEGIN);
56 if (!eword_begin_p) {
57 g_string_append(outbuf, p);
58 break;
59 }
60 encoding_begin_p = strchr(eword_begin_p + 2, '?');
61 if (!encoding_begin_p) {
62 g_string_append(outbuf, p);
63 break;
64 }
65 text_begin_p = strchr(encoding_begin_p + 1, '?');
66 if (!text_begin_p) {
67 g_string_append(outbuf, p);
68 break;
69 }
70 eword_end_p = strstr(text_begin_p + 1, ENCODED_WORD_END);
71 if (!eword_end_p) {
72 g_string_append(outbuf, p);
73 break;
74 }
75
76 if (p == encoded_str) {
77 g_string_append_len(outbuf, p, eword_begin_p - p);
78 p = eword_begin_p;
79 } else {
80 /* ignore spaces between encoded words */
81 const gchar *sp;
82
83 for (sp = p; sp < eword_begin_p; sp++) {
84 if (!g_ascii_isspace(*sp)) {
85 g_string_append_len
86 (outbuf, p, eword_begin_p - p);
87 p = eword_begin_p;
88 break;
89 }
90 }
91 }
92
93 len = MIN(sizeof(charset) - 1,
94 encoding_begin_p - (eword_begin_p + 2));
95 memcpy(charset, eword_begin_p + 2, len);
96 charset[len] = '\0';
97 encoding = g_ascii_toupper(*(encoding_begin_p + 1));
98
99 if (encoding == 'B') {
100 decoded_text = g_malloc
101 (eword_end_p - (text_begin_p + 1) + 1);
102 len = base64_decode((guchar *)decoded_text,
103 text_begin_p + 1,
104 eword_end_p - (text_begin_p + 1));
105 decoded_text[len] = '\0';
106 } else if (encoding == 'Q') {
107 decoded_text = g_malloc
108 (eword_end_p - (text_begin_p + 1) + 1);
109 len = qp_decode_q_encoding
110 ((guchar *)decoded_text, text_begin_p + 1,
111 eword_end_p - (text_begin_p + 1));
112 } else {
113 g_string_append_len(outbuf, p, eword_end_p + 2 - p);
114 p = eword_end_p + 2;
115 continue;
116 }
117
118 /* convert to UTF-8 */
119 conv_str = conv_codeset_strdup(decoded_text, charset, NULL);
120 if (!conv_str)
121 conv_str = conv_utf8todisp(decoded_text, NULL);
122 g_string_append(outbuf, conv_str);
123 g_free(conv_str);
124
125 g_free(decoded_text);
126
127 p = eword_end_p + 2;
128 }
129
130 out_str = outbuf->str;
131 out_len = outbuf->len;
132 g_string_free(outbuf, FALSE);
133
134 return g_realloc(out_str, out_len + 1);
135}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __UNMIME_H__
21#define __UNMIME_H__
22
23#include <glib.h>
24
25gchar *unmime_header (const gchar *encoded_str);
26
27#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2017 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <stdio.h>
29#include <string.h>
30#include <ctype.h>
31#include <errno.h>
32#include <stdlib.h>
33#include <sys/stat.h>
34#include <fcntl.h>
35#include <unistd.h>
36#include <stdarg.h>
37#include <sys/types.h>
38#if HAVE_SYS_WAIT_H
39# include <sys/wait.h>
40#endif
41#include <dirent.h>
42#include <time.h>
43
44#ifdef G_OS_WIN32
45#ifndef WINVER
46# define WINVER 0x0500
47#endif
48# include <windows.h>
49# include <wchar.h>
50# include <direct.h>
51# include <io.h>
52# include <shlobj.h>
53#endif
54
55#ifdef __APPLE__
56# include <AppKit/AppKit.h>
57#endif
58
59#include "utils.h"
60#include "socket.h"
61
62#define BUFFSIZE 8192
63
64static gboolean debug_mode = FALSE;
65
66
67#if !GLIB_CHECK_VERSION(2, 7, 0) && !defined(G_OS_UNIX)
68gint g_chdir(const gchar *path)
69{
70#ifdef G_OS_WIN32
71 if (G_WIN32_HAVE_WIDECHAR_API()) {
72 wchar_t *wpath;
73 gint retval;
74 gint save_errno;
75
76 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
77 if (wpath == NULL) {
78 errno = EINVAL;
79 return -1;
80 }
81
82 retval = _wchdir(wpath);
83 save_errno = errno;
84
85 g_free(wpath);
86
87 errno = save_errno;
88 return retval;
89 } else {
90 gchar *cp_path;
91 gint retval;
92 gint save_errno;
93
94 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
95 if (cp_path == NULL) {
96 errno = EINVAL;
97 return -1;
98 }
99
100 retval = chdir(cp_path);
101 save_errno = errno;
102
103 g_free(cp_path);
104
105 errno = save_errno;
106 return retval;
107 }
108#else
109 return chdir(path);
110#endif
111}
112
113gint g_chmod(const gchar *path, gint mode)
114{
115#ifdef G_OS_WIN32
116 if (G_WIN32_HAVE_WIDECHAR_API()) {
117 wchar_t *wpath;
118 gint retval;
119 gint save_errno;
120
121 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
122 if (wpath == NULL) {
123 errno = EINVAL;
124 return -1;
125 }
126
127 retval = _wchmod(wpath, mode);
128 save_errno = errno;
129
130 g_free(wpath);
131
132 errno = save_errno;
133 return retval;
134 } else {
135 gchar *cp_path;
136 gint retval;
137 gint save_errno;
138
139 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
140 if (cp_path == NULL) {
141 errno = EINVAL;
142 return -1;
143 }
144
145 retval = chmod(cp_path, mode);
146 save_errno = errno;
147
148 g_free(cp_path);
149
150 errno = save_errno;
151 return retval;
152 }
153#else
154 return chmod(path, mode);
155#endif
156}
157#endif /* GLIB_CHECK_VERSION && G_OS_UNIX */
158
159#ifndef G_OS_UNIX
160gint syl_link(const gchar *src, const gchar *dest)
161{
162#ifdef G_OS_WIN32
163 wchar_t *wsrc;
164 wchar_t *wdest;
165 gint retval;
166 gint save_errno;
167
168 wsrc = g_utf8_to_utf16(src, -1, NULL, NULL, NULL);
169 if (wsrc == NULL) {
170 errno = EINVAL;
171 return -1;
172 }
173 wdest = g_utf8_to_utf16(dest, -1, NULL, NULL, NULL);
174 if (wdest == NULL) {
175 g_free(wsrc);
176 errno = EINVAL;
177 return -1;
178 }
179
180 errno = 0;
181 if (CreateHardLinkW(wdest, wsrc, NULL)) {
182 retval = 0;
183 /* debug_print("hard link created: %s -> %s\n", src, dest); */
184 } else {
185 retval = -1;
186 switch (GetLastError()) {
187 case ERROR_FILE_NOT_FOUND:
188 case ERROR_PATH_NOT_FOUND:
189 errno = ENOENT; break;
190 case ERROR_ACCESS_DENIED:
191 case ERROR_LOCK_VIOLATION:
192 case ERROR_SHARING_VIOLATION:
193 errno = EACCES; break;
194 case ERROR_NOT_SAME_DEVICE:
195 errno = EXDEV; break;
196 case ERROR_FILE_EXISTS:
197 case ERROR_ALREADY_EXISTS:
198 errno = EEXIST; break;
199 case ERROR_TOO_MANY_LINKS:
200 errno = EMLINK; break;
201 default:
202 errno = EIO; break;
203 }
204 }
205 save_errno = errno;
206
207 g_free(wdest);
208 g_free(wsrc);
209
210 errno = save_errno;
211 return retval;
212#else
213 return link(src, dest);
214#endif
215}
216#endif /* !G_OS_UNIX */
217
218void list_free_strings(GList *list)
219{
220 list = g_list_first(list);
221
222 while (list != NULL) {
223 g_free(list->data);
224 list = list->next;
225 }
226}
227
228void slist_free_strings(GSList *list)
229{
230 while (list != NULL) {
231 g_free(list->data);
232 list = list->next;
233 }
234}
235
236static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
237{
238 g_free(key);
239}
240
241void hash_free_strings(GHashTable *table)
242{
243 g_hash_table_foreach(table, hash_free_strings_func, NULL);
244}
245
246static void hash_free_value_mem_func(gpointer key, gpointer value,
247 gpointer data)
248{
249 g_free(value);
250}
251
252void hash_free_value_mem(GHashTable *table)
253{
254 g_hash_table_foreach(table, hash_free_value_mem_func, NULL);
255}
256
257gint str_case_equal(gconstpointer v, gconstpointer v2)
258{
259 return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
260}
261
262guint str_case_hash(gconstpointer key)
263{
264 const gchar *p = key;
265 guint h = *p;
266
267 if (h) {
268 h = g_ascii_tolower(h);
269 for (p += 1; *p != '\0'; p++)
270 h = (h << 5) - h + g_ascii_tolower(*p);
271 }
272
273 return h;
274}
275
276void ptr_array_free_strings(GPtrArray *array)
277{
278 gint i;
279 gchar *str;
280
281 g_return_if_fail(array != NULL);
282
283 for (i = 0; i < array->len; i++) {
284 str = g_ptr_array_index(array, i);
285 g_free(str);
286 }
287}
288
289gboolean str_find(const gchar *haystack, const gchar *needle)
290{
291 return strstr(haystack, needle) != NULL ? TRUE : FALSE;
292}
293
294gboolean str_case_find(const gchar *haystack, const gchar *needle)
295{
296 return strcasestr(haystack, needle) != NULL ? TRUE : FALSE;
297}
298
299gboolean str_find_equal(const gchar *haystack, const gchar *needle)
300{
301 return strcmp(haystack, needle) == 0;
302}
303
304gboolean str_case_find_equal(const gchar *haystack, const gchar *needle)
305{
306 return g_ascii_strcasecmp(haystack, needle) == 0;
307}
308
309gint to_number(const gchar *nstr)
310{
311 register const gchar *p;
312
313 if (*nstr == '\0') return -1;
314
315 for (p = nstr; *p != '\0'; p++)
316 if (!g_ascii_isdigit(*p)) return -1;
317
318 return atoi(nstr);
319}
320
321guint to_unumber(const gchar *nstr)
322{
323 register const gchar *p;
324 gulong val;
325
326 if (*nstr == '\0') return 0;
327
328 for (p = nstr; *p != '\0'; p++)
329 if (!g_ascii_isdigit(*p)) return 0;
330
331 errno = 0;
332 val = strtoul(nstr, NULL, 10);
333 if (val == ULONG_MAX && errno != 0)
334 val = 0;
335
336 return (guint)val;
337}
338
339/* convert integer into string,
340 nstr must be not lower than 11 characters length */
341gchar *itos_buf(gchar *nstr, gint n)
342{
343 g_snprintf(nstr, 11, "%d", n);
344 return nstr;
345}
346
347/* convert integer into string */
348gchar *itos(gint n)
349{
350 static gchar nstr[11];
351
352 return itos_buf(nstr, n);
353}
354
355gchar *utos_buf(gchar *nstr, guint n)
356{
357 g_snprintf(nstr, 11, "%u", n);
358 return nstr;
359}
360
361gchar *to_human_readable_buf(gchar *buf, size_t bufsize, gint64 size)
362{
363 if (size < 1024)
364 g_snprintf(buf, bufsize, "%dB", (gint)size);
365 else if ((size >> 10) < 1024)
366 g_snprintf(buf, bufsize, "%.1fKB", (gfloat)size / (1 << 10));
367 else if ((size >> 20) < 1024)
368 g_snprintf(buf, bufsize, "%.2fMB", (gfloat)size / (1 << 20));
369 else
370 g_snprintf(buf, bufsize, "%.2fGB", (gfloat)size / (1 << 30));
371
372 return buf;
373}
374
375gchar *to_human_readable(gint64 size)
376{
377 static gchar str[16];
378
379 return to_human_readable_buf(str, sizeof(str), size);
380}
381
382/* strcmp with NULL-checking */
383gint strcmp2(const gchar *s1, const gchar *s2)
384{
385 if (s1 == NULL || s2 == NULL)
386 return -1;
387 else
388 return strcmp(s1, s2);
389}
390
391/* compare paths */
392gint path_cmp(const gchar *s1, const gchar *s2)
393{
394 gint len1, len2;
395#ifdef G_OS_WIN32
396 gchar *s1_, *s2_;
397#endif
398
399 if (s1 == NULL || s2 == NULL) return -1;
400 if (*s1 == '\0' || *s2 == '\0') return -1;
401
402 len1 = strlen(s1);
403 len2 = strlen(s2);
404
405#ifdef G_OS_WIN32
406 Xstrdup_a(s1_, s1, return -1);
407 Xstrdup_a(s2_, s2, return -1);
408 subst_char(s1_, '/', G_DIR_SEPARATOR);
409 subst_char(s2_, '/', G_DIR_SEPARATOR);
410 if (s1_[len1 - 1] == G_DIR_SEPARATOR) len1--;
411 if (s2_[len2 - 1] == G_DIR_SEPARATOR) len2--;
412
413 return strncmp(s1_, s2_, MAX(len1, len2));
414#else
415 if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
416 if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
417
418 return strncmp(s1, s2, MAX(len1, len2));
419#endif
420}
421
422/* return TRUE if parent is equal to or ancestor of child */
423gboolean is_path_parent(const gchar *parent, const gchar *child)
424{
425 gint plen;
426 const gchar *base;
427
428 g_return_val_if_fail(parent != NULL, FALSE);
429 g_return_val_if_fail(child != NULL, FALSE);
430
431 plen = strlen(parent);
432 while (plen > 0 && G_IS_DIR_SEPARATOR(parent[plen - 1]))
433 plen--;
434
435#ifdef G_OS_WIN32
436 if (!g_ascii_strncasecmp(parent, child, plen)) {
437#else
438 if (!strncmp(parent, child, plen)) {
439#endif
440 base = child + plen;
441 if (!G_IS_DIR_SEPARATOR(*base) && *base != '\0')
442 return FALSE;
443 return TRUE;
444 }
445
446 return FALSE;
447}
448
449/* remove trailing return code */
450gchar *strretchomp(gchar *str)
451{
452 register gchar *s;
453
454 if (!*str) return str;
455
456 for (s = str + strlen(str) - 1;
457 s >= str && (*s == '\n' || *s == '\r');
458 s--)
459 *s = '\0';
460
461 return str;
462}
463
464/* remove trailing character */
465gchar *strtailchomp(gchar *str, gchar tail_char)
466{
467 register gchar *s;
468
469 if (!*str) return str;
470 if (tail_char == '\0') return str;
471
472 for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
473 *s = '\0';
474
475 return str;
476}
477
478/* remove CR (carriage return) */
479gchar *strcrchomp(gchar *str)
480{
481 register gchar *s;
482
483 if (!*str) return str;
484
485 s = str + strlen(str) - 1;
486 if (*s == '\n' && s > str && *(s - 1) == '\r') {
487 *(s - 1) = '\n';
488 *s = '\0';
489 }
490
491 return str;
492}
493
494/* Similar to `strstr' but this function ignores the case of both strings. */
495gchar *strcasestr(const gchar *haystack, const gchar *needle)
496{
497 register size_t haystack_len, needle_len;
498
499 haystack_len = strlen(haystack);
500 needle_len = strlen(needle);
501
502 if (haystack_len < needle_len || needle_len == 0)
503 return NULL;
504
505 while (haystack_len >= needle_len) {
506 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
507 return (gchar *)haystack;
508 else {
509 haystack++;
510 haystack_len--;
511 }
512 }
513
514 return NULL;
515}
516
517gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
518 gconstpointer needle, size_t needlelen)
519{
520 const gchar *haystack_ = (const gchar *)haystack;
521 const gchar *needle_ = (const gchar *)needle;
522 const gchar *haystack_cur = (const gchar *)haystack;
523 size_t haystack_left = haystacklen;
524
525 if (needlelen == 1)
526 return memchr(haystack_, *needle_, haystacklen);
527
528 while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left))
529 != NULL) {
530 if (haystacklen - (haystack_cur - haystack_) < needlelen)
531 break;
532 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
533 return (gpointer)haystack_cur;
534 else {
535 haystack_cur++;
536 haystack_left = haystacklen - (haystack_cur - haystack_);
537 }
538 }
539
540 return NULL;
541}
542
543/* Copy no more than N characters of SRC to DEST, with NULL terminating. */
544gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
545{
546 register const gchar *s = src;
547 register gchar *d = dest;
548
549 while (--n && *s)
550 *d++ = *s++;
551 *d = '\0';
552
553 return dest;
554}
555
556/* Similar to g_str_has_suffix() but case-insensitive */
557gboolean str_has_suffix_case(const gchar *str, const gchar *suffix)
558{
559 size_t len, s_len;
560
561 if (!str || !suffix)
562 return FALSE;
563
564 len = strlen(str);
565 s_len = strlen(suffix);
566
567 if (s_len > len)
568 return FALSE;
569
570 return (g_ascii_strcasecmp(str + (len - s_len), suffix) == 0);
571}
572
573gint str_find_format_times(const gchar *haystack, gchar ch)
574{
575 gint n = 0;
576 const gchar *p = haystack;
577
578 while ((p = strchr(p, '%')) != NULL) {
579 ++p;
580 if (*p == '%') {
581 ++p;
582 } else if (*p == ch) {
583 ++p;
584 ++n;
585 } else
586 return -1;
587 }
588
589 return n;
590}
591
592/* Examine if next block is non-ASCII string */
593gboolean is_next_nonascii(const gchar *s)
594{
595 const gchar *p;
596 gboolean in_quote = FALSE;
597
598 /* skip head space */
599 for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
600 ;
601 while (*p != '\0') {
602 if (!in_quote && g_ascii_isspace(*p))
603 break;
604 if (*p == '"')
605 in_quote ^= TRUE;
606 else if (*(guchar *)p > 127 || *(guchar *)p < 32)
607 return TRUE;
608 ++p;
609 }
610
611 return FALSE;
612}
613
614gint get_next_word_len(const gchar *s)
615{
616 const gchar *p = s;
617 gboolean in_quote = FALSE;
618
619 while (*p != '\0') {
620 if (!in_quote && g_ascii_isspace(*p))
621 break;
622 if (*p == '"')
623 in_quote ^= TRUE;
624 ++p;
625 }
626
627 return p - s;
628}
629
630/* compare subjects */
631gint subject_compare(const gchar *s1, const gchar *s2)
632{
633 gchar *str1, *str2;
634
635 if (!s1 || !s2) return -1;
636 if (!*s1 || !*s2) return -1;
637
638 Xstrdup_a(str1, s1, return -1);
639 Xstrdup_a(str2, s2, return -1);
640
641 trim_subject_for_compare(str1);
642 trim_subject_for_compare(str2);
643
644 if (!*str1 || !*str2) return -1;
645
646 return strcmp(str1, str2);
647}
648
649gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
650{
651 gchar *str1, *str2;
652
653 if (!s1 || !s2) return -1;
654
655 Xstrdup_a(str1, s1, return -1);
656 Xstrdup_a(str2, s2, return -1);
657
658 trim_subject_for_sort(str1);
659 trim_subject_for_sort(str2);
660
661 return g_ascii_strcasecmp(str1, str2);
662}
663
664void trim_subject_for_compare(gchar *str)
665{
666 gchar *srcp;
667
668 eliminate_parenthesis(str, '[', ']');
669 eliminate_parenthesis(str, '(', ')');
670 g_strstrip(str);
671
672 while (!g_ascii_strncasecmp(str, "Re:", 3)) {
673 srcp = str + 3;
674 while (g_ascii_isspace(*srcp)) srcp++;
675 memmove(str, srcp, strlen(srcp) + 1);
676 }
677}
678
679void trim_subject_for_sort(gchar *str)
680{
681 gchar *srcp;
682
683 g_strstrip(str);
684
685 while (!g_ascii_strncasecmp(str, "Re:", 3)) {
686 srcp = str + 3;
687 while (g_ascii_isspace(*srcp)) srcp++;
688 memmove(str, srcp, strlen(srcp) + 1);
689 }
690}
691
692void trim_subject(gchar *str)
693{
694 register gchar *srcp, *destp;
695 gchar op, cl;
696 gint in_brace;
697
698 destp = str;
699 while (!g_ascii_strncasecmp(destp, "Re:", 3)) {
700 destp += 3;
701 while (g_ascii_isspace(*destp)) destp++;
702 }
703
704 if (*destp == '[') {
705 op = '[';
706 cl = ']';
707 } else if (*destp == '(') {
708 op = '(';
709 cl = ')';
710 } else
711 return;
712
713 srcp = destp + 1;
714 in_brace = 1;
715 while (*srcp) {
716 if (*srcp == op)
717 in_brace++;
718 else if (*srcp == cl)
719 in_brace--;
720 srcp++;
721 if (in_brace == 0)
722 break;
723 }
724 while (g_ascii_isspace(*srcp)) srcp++;
725 memmove(destp, srcp, strlen(srcp) + 1);
726}
727
728void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
729{
730 register gchar *srcp, *destp;
731 gint in_brace;
732
733 srcp = destp = str;
734
735 while ((destp = strchr(destp, op))) {
736 in_brace = 1;
737 srcp = destp + 1;
738 while (*srcp) {
739 if (*srcp == op)
740 in_brace++;
741 else if (*srcp == cl)
742 in_brace--;
743 srcp++;
744 if (in_brace == 0)
745 break;
746 }
747 while (g_ascii_isspace(*srcp)) srcp++;
748 memmove(destp, srcp, strlen(srcp) + 1);
749 }
750}
751
752void extract_parenthesis(gchar *str, gchar op, gchar cl)
753{
754 register gchar *srcp, *destp;
755 gint in_brace;
756
757 srcp = destp = str;
758
759 while ((srcp = strchr(destp, op))) {
760 if (destp > str)
761 *destp++ = ' ';
762 memmove(destp, srcp + 1, strlen(srcp));
763 in_brace = 1;
764 while(*destp) {
765 if (*destp == op)
766 in_brace++;
767 else if (*destp == cl)
768 in_brace--;
769
770 if (in_brace == 0)
771 break;
772
773 destp++;
774 }
775 }
776 *destp = '\0';
777}
778
779void extract_parenthesis_with_escape(gchar *str, gchar op, gchar cl)
780{
781 register gchar *srcp, *destp;
782 gint in_brace;
783
784 srcp = destp = str;
785
786 while ((srcp = strchr(srcp, op))) {
787 if (destp > str)
788 *destp++ = ' ';
789 ++srcp;
790 in_brace = 1;
791 while (*srcp) {
792 if (*srcp == op)
793 in_brace++;
794 else if (*srcp == cl)
795 in_brace--;
796
797 if (in_brace == 0)
798 break;
799
800 if (*srcp == '\\' && *(srcp + 1) != '\0')
801 ++srcp;
802
803 *destp++ = *srcp++;
804 }
805 }
806 *destp = '\0';
807}
808
809void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
810 gchar op, gchar cl)
811{
812 register gchar *srcp, *destp;
813 gint in_brace;
814 gboolean in_quote = FALSE;
815
816 srcp = destp = str;
817
818 while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
819 if (destp > str)
820 *destp++ = ' ';
821 memmove(destp, srcp + 1, strlen(srcp));
822 in_brace = 1;
823 while(*destp) {
824 if (*destp == op && !in_quote)
825 in_brace++;
826 else if (*destp == cl && !in_quote)
827 in_brace--;
828 else if (*destp == quote_chr)
829 in_quote ^= TRUE;
830
831 if (in_brace == 0)
832 break;
833
834 destp++;
835 }
836 }
837 *destp = '\0';
838}
839
840void eliminate_quote(gchar *str, gchar quote_chr)
841{
842 register gchar *srcp, *destp;
843
844 srcp = destp = str;
845
846 while ((destp = strchr(destp, quote_chr))) {
847 if ((srcp = strchr(destp + 1, quote_chr))) {
848 srcp++;
849 while (g_ascii_isspace(*srcp)) srcp++;
850 memmove(destp, srcp, strlen(srcp) + 1);
851 } else {
852 *destp = '\0';
853 break;
854 }
855 }
856}
857
858void extract_quote(gchar *str, gchar quote_chr)
859{
860 register gchar *p;
861
862 if ((str = strchr(str, quote_chr))) {
863 if ((p = strchr(str + 1, quote_chr))) {
864 *p = '\0';
865 memmove(str, str + 1, p - str);
866 }
867 }
868}
869
870void extract_quote_with_escape(gchar *str, gchar quote_chr)
871{
872 register gchar *sp, *dp;
873
874 if ((sp = strchr(str, quote_chr))) {
875 dp = sp;
876 ++sp;
877 while (*sp) {
878 if (*sp == quote_chr)
879 break;
880 else if (*sp == '\\' && *(sp + 1) != '\0')
881 ++sp;
882
883 *dp++ = *sp++;
884 }
885 *dp = '\0';
886 }
887}
888
889void eliminate_address_comment(gchar *str)
890{
891 register gchar *srcp, *destp;
892 gint in_brace;
893
894 srcp = destp = str;
895
896 while ((destp = strchr(destp, '"'))) {
897 if ((srcp = strchr(destp + 1, '"'))) {
898 srcp++;
899 if (*srcp == '@') {
900 destp = srcp + 1;
901 } else {
902 while (g_ascii_isspace(*srcp)) srcp++;
903 memmove(destp, srcp, strlen(srcp) + 1);
904 }
905 } else {
906 *destp = '\0';
907 break;
908 }
909 }
910
911 srcp = destp = str;
912
913 while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
914 in_brace = 1;
915 srcp = destp + 1;
916 while (*srcp) {
917 if (*srcp == '(')
918 in_brace++;
919 else if (*srcp == ')')
920 in_brace--;
921 srcp++;
922 if (in_brace == 0)
923 break;
924 }
925 while (g_ascii_isspace(*srcp)) srcp++;
926 memmove(destp, srcp, strlen(srcp) + 1);
927 }
928}
929
930gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
931{
932 gboolean in_quote = FALSE;
933
934 while (*str) {
935 if (*str == c && !in_quote)
936 return (gchar *)str;
937 if (*str == quote_chr)
938 in_quote ^= TRUE;
939 str++;
940 }
941
942 return NULL;
943}
944
945gchar *strrchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
946{
947 gboolean in_quote = FALSE;
948 const gchar *p;
949
950 p = str + strlen(str) - 1;
951 while (p >= str) {
952 if (*p == c && !in_quote)
953 return (gchar *)p;
954 if (*p == quote_chr)
955 in_quote ^= TRUE;
956 p--;
957 }
958
959 return NULL;
960}
961
962void extract_address(gchar *str)
963{
964 eliminate_address_comment(str);
965 if (strchr_with_skip_quote(str, '"', '<'))
966 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
967 g_strstrip(str);
968}
969
970void extract_list_id_str(gchar *str)
971{
972 if (strchr_with_skip_quote(str, '"', '<'))
973 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
974 g_strstrip(str);
975}
976
977gchar *extract_addresses(const gchar *str)
978{
979 GString *new_str;
980 GSList *addr_list, *cur;
981
982 if (!str)
983 return NULL;
984
985 addr_list = address_list_append(NULL, str);
986
987 new_str = g_string_new(NULL);
988
989 for (cur = addr_list; cur != NULL; cur = cur->next) {
990 g_string_append(new_str, (gchar *)cur->data);
991 if (cur->next)
992 g_string_append(new_str, ", ");
993 }
994
995 slist_free_strings(addr_list);
996 g_slist_free(addr_list);
997
998 return g_string_free(new_str, FALSE);
999}
1000
1001gchar *normalize_address_field(const gchar *str)
1002{
1003 GString *new_str;
1004 GSList *addr_list, *cur;
1005 gchar *addr, *p, *q, *r;
1006 gchar *ret_str;
1007
1008 addr_list = address_list_append_orig(NULL, str);
1009
1010 new_str = g_string_new(NULL);
1011
1012 for (cur = addr_list; cur != NULL; cur = cur->next) {
1013 p = addr = (gchar *)cur->data;
1014 q = strchr_with_skip_quote(p, '"', '<');
1015 if (q && q > p) {
1016 r = q - 1;
1017 while (r > p && g_ascii_isspace(*r))
1018 --r;
1019 g_string_append_len(new_str, p, r - p + 1);
1020 g_string_append_c(new_str, ' ');
1021 p = q;
1022 }
1023 if (*p == '<') {
1024 q = strchr(p, '>');
1025 if (q) {
1026 r = q + 1;
1027 if (*r) {
1028 while (g_ascii_isspace(*r))
1029 ++r;
1030 g_string_append(new_str, r);
1031 if (new_str->len > 0 &&
1032 !g_ascii_isspace
1033 (new_str->str[new_str->len - 1]))
1034 g_string_append_c(new_str, ' ');
1035 }
1036 g_string_append_len(new_str, p, q - p + 1);
1037 } else {
1038 g_string_append(new_str, p);
1039 g_string_append_c(new_str, '>');
1040 }
1041 } else
1042 g_string_append(new_str, p);
1043
1044 if (cur->next)
1045 g_string_append(new_str, ", ");
1046 }
1047
1048 slist_free_strings(addr_list);
1049 ret_str = new_str->str;
1050 g_string_free(new_str, FALSE);
1051
1052 return ret_str;
1053}
1054
1055gboolean address_equal(const gchar *addr1, const gchar *addr2)
1056{
1057 gchar *addr1_, *addr2_;
1058
1059 if (!addr1 || !addr2)
1060 return FALSE;
1061
1062 Xstrdup_a(addr1_, addr1, return FALSE);
1063 Xstrdup_a(addr2_, addr2, return FALSE);
1064
1065 extract_address(addr1_);
1066 extract_address(addr2_);
1067
1068 return strcmp(addr1_, addr2_) == 0;
1069}
1070
1071GSList *address_list_append_orig(GSList *addr_list, const gchar *str)
1072{
1073 const gchar *p = str, *q;
1074 gchar *addr;
1075
1076 if (!str) return addr_list;
1077
1078 while (*p) {
1079 if (*p == ',' || g_ascii_isspace(*p)) {
1080 ++p;
1081 } else if ((q = strchr_with_skip_quote(p, '"', ','))) {
1082 addr = g_strndup(p, q - p);
1083 g_strstrip(addr);
1084 addr_list = g_slist_append(addr_list, addr);
1085 p = q + 1;
1086 } else {
1087 addr = g_strdup(p);
1088 g_strstrip(addr);
1089 addr_list = g_slist_append(addr_list, addr);
1090 break;
1091 }
1092 }
1093
1094 return addr_list;
1095}
1096
1097GSList *address_list_append(GSList *addr_list, const gchar *str)
1098{
1099 gchar *work;
1100 gchar *workp;
1101
1102 if (!str) return addr_list;
1103
1104 Xstrdup_a(work, str, return addr_list);
1105
1106 eliminate_address_comment(work);
1107 workp = work;
1108
1109 while (workp && *workp) {
1110 gchar *p, *next;
1111
1112 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
1113 *p = '\0';
1114 next = p + 1;
1115 } else
1116 next = NULL;
1117
1118 if (strchr_with_skip_quote(workp, '"', '<'))
1119 extract_parenthesis_with_skip_quote
1120 (workp, '"', '<', '>');
1121
1122 g_strstrip(workp);
1123 if (*workp)
1124 addr_list = g_slist_append(addr_list, g_strdup(workp));
1125
1126 workp = next;
1127 }
1128
1129 return addr_list;
1130}
1131
1132GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
1133{
1134 const gchar *strp;
1135
1136 if (!str) return msgid_list;
1137 strp = str;
1138
1139 while (strp && *strp) {
1140 const gchar *start, *end;
1141 gchar *msgid;
1142
1143 if ((start = strchr(strp, '<')) != NULL) {
1144 end = strchr(start + 1, '>');
1145 if (!end) break;
1146 } else
1147 break;
1148
1149 msgid = g_strndup(start + 1, end - start - 1);
1150 g_strstrip(msgid);
1151 if (*msgid)
1152 msgid_list = g_slist_prepend(msgid_list, msgid);
1153 else
1154 g_free(msgid);
1155
1156 strp = end + 1;
1157 }
1158
1159 return msgid_list;
1160}
1161
1162GSList *references_list_append(GSList *msgid_list, const gchar *str)
1163{
1164 GSList *list;
1165
1166 list = references_list_prepend(NULL, str);
1167 list = g_slist_reverse(list);
1168 msgid_list = g_slist_concat(msgid_list, list);
1169
1170 return msgid_list;
1171}
1172
1173GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
1174{
1175 gchar *work;
1176 gchar *workp;
1177
1178 if (!str) return group_list;
1179
1180 Xstrdup_a(work, str, return group_list);
1181
1182 workp = work;
1183
1184 while (workp && *workp) {
1185 gchar *p, *next;
1186
1187 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
1188 *p = '\0';
1189 next = p + 1;
1190 } else
1191 next = NULL;
1192
1193 g_strstrip(workp);
1194 if (*workp)
1195 group_list = g_slist_append(group_list,
1196 g_strdup(workp));
1197
1198 workp = next;
1199 }
1200
1201 return group_list;
1202}
1203
1204GList *add_history(GList *list, const gchar *str)
1205{
1206 GList *old;
1207
1208 g_return_val_if_fail(str != NULL, list);
1209
1210 old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
1211 if (old) {
1212 g_free(old->data);
1213 list = g_list_remove(list, old->data);
1214 } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
1215 GList *last;
1216
1217 last = g_list_last(list);
1218 if (last) {
1219 g_free(last->data);
1220 list = g_list_remove(list, last->data);
1221 }
1222 }
1223
1224 list = g_list_prepend(list, g_strdup(str));
1225
1226 return list;
1227}
1228
1229void remove_return(gchar *str)
1230{
1231 register gchar *p = str;
1232
1233 while (*p) {
1234 if (*p == '\n' || *p == '\r')
1235 memmove(p, p + 1, strlen(p));
1236 else
1237 p++;
1238 }
1239}
1240
1241void remove_space(gchar *str)
1242{
1243 register gchar *p = str;
1244 register gint spc;
1245
1246 while (*p) {
1247 spc = 0;
1248 while (g_ascii_isspace(*(p + spc)))
1249 spc++;
1250 if (spc)
1251 memmove(p, p + spc, strlen(p + spc) + 1);
1252 else
1253 p++;
1254 }
1255}
1256
1257void unfold_line(gchar *str)
1258{
1259 register gchar *p = str;
1260 register gint spc;
1261
1262 while (*p) {
1263 if (*p == '\n' || *p == '\r') {
1264 *p++ = ' ';
1265 spc = 0;
1266 while (g_ascii_isspace(*(p + spc)))
1267 spc++;
1268 if (spc)
1269 memmove(p, p + spc, strlen(p + spc) + 1);
1270 } else
1271 p++;
1272 }
1273}
1274
1275void subst_char(gchar *str, gchar orig, gchar subst)
1276{
1277 register gchar *p = str;
1278
1279 while (*p) {
1280 if (*p == orig)
1281 *p = subst;
1282 p++;
1283 }
1284}
1285
1286void subst_chars(gchar *str, gchar *orig, gchar subst)
1287{
1288 register gchar *p = str;
1289
1290 while (*p) {
1291 if (strchr(orig, *p) != NULL)
1292 *p = subst;
1293 ++p;
1294 }
1295}
1296
1297void subst_null(gchar *str, gint len, gchar subst)
1298{
1299 register gchar *p = str;
1300
1301 while (len--) {
1302 if (*p == '\0')
1303 *p = subst;
1304 ++p;
1305 }
1306}
1307
1308void subst_control(gchar *str, gchar subst)
1309{
1310 register gchar *p = str;
1311
1312 while (*p) {
1313 if (g_ascii_iscntrl(*p))
1314 *p = subst;
1315 ++p;
1316 }
1317}
1318
1319void subst_for_filename(gchar *str)
1320{
1321 subst_chars(str, " \t\r\n\"'\\/:;*?<>|", '_');
1322}
1323
1324gchar *get_alt_filename(const gchar *filename, gint count)
1325{
1326 const gchar *ext;
1327 gchar *alt_filename;
1328
1329 ext = strrchr(filename, '.');
1330
1331 if (ext) {
1332 gchar *base;
1333
1334 base = g_strndup(filename, ext - filename);
1335 alt_filename = g_strdup_printf("%s-%d%s", base, count, ext);
1336 g_free(base);
1337 } else
1338 alt_filename = g_strdup_printf("%s-%d", filename, count);
1339
1340 return alt_filename;
1341}
1342
1343gboolean is_header_line(const gchar *str)
1344{
1345 if (str[0] == ':') return FALSE;
1346
1347 while (*str != '\0' && *str != ' ') {
1348 if (*str == ':')
1349 return TRUE;
1350 str++;
1351 }
1352
1353 return FALSE;
1354}
1355
1356gboolean is_ascii_str(const gchar *str)
1357{
1358 const guchar *p = (const guchar *)str;
1359
1360 while (*p != '\0') {
1361 if (*p != '\t' && *p != ' ' &&
1362 *p != '\r' && *p != '\n' &&
1363 (*p < 32 || *p >= 127))
1364 return FALSE;
1365 p++;
1366 }
1367
1368 return TRUE;
1369}
1370
1371gint get_quote_level(const gchar *str)
1372{
1373 const gchar *first_pos;
1374 const gchar *last_pos;
1375 const gchar *p = str;
1376 gint quote_level = -1;
1377
1378 /* speed up line processing by only searching to the last '>' */
1379 if ((first_pos = strchr(str, '>')) != NULL) {
1380 /* skip a line if it contains a '<' before the initial '>' */
1381 if (memchr(str, '<', first_pos - str) != NULL)
1382 return -1;
1383 last_pos = strrchr(first_pos, '>');
1384 } else
1385 return -1;
1386
1387 while (p <= last_pos) {
1388 while (p < last_pos) {
1389 if (g_ascii_isspace(*p))
1390 p++;
1391 else
1392 break;
1393 }
1394
1395 if (*p == '>')
1396 quote_level++;
1397 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1398 /* any characters are allowed except '-' and space */
1399 while (*p != '-' && *p != '>' && !g_ascii_isspace(*p) &&
1400 p < last_pos)
1401 p++;
1402 if (*p == '>')
1403 quote_level++;
1404 else
1405 break;
1406 }
1407
1408 p++;
1409 }
1410
1411 return quote_level;
1412}
1413
1414gint check_line_length(const gchar *str, gint max_chars, gint *line)
1415{
1416 const gchar *p = str, *q;
1417 gint cur_line = 0, len;
1418
1419 while ((q = strchr(p, '\n')) != NULL) {
1420 len = q - p + 1;
1421 if (len > max_chars) {
1422 if (line)
1423 *line = cur_line;
1424 return -1;
1425 }
1426 p = q + 1;
1427 ++cur_line;
1428 }
1429
1430 len = strlen(p);
1431 if (len > max_chars) {
1432 if (line)
1433 *line = cur_line;
1434 return -1;
1435 }
1436
1437 return 0;
1438}
1439
1440gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1441{
1442 register guint haystack_len, needle_len;
1443 gboolean in_squote = FALSE, in_dquote = FALSE;
1444
1445 haystack_len = strlen(haystack);
1446 needle_len = strlen(needle);
1447
1448 if (haystack_len < needle_len || needle_len == 0)
1449 return NULL;
1450
1451 while (haystack_len >= needle_len) {
1452 if (!in_squote && !in_dquote &&
1453 !strncmp(haystack, needle, needle_len))
1454 return (gchar *)haystack;
1455
1456 /* 'foo"bar"' -> foo"bar"
1457 "foo'bar'" -> foo'bar' */
1458 if (*haystack == '\'') {
1459 if (in_squote)
1460 in_squote = FALSE;
1461 else if (!in_dquote)
1462 in_squote = TRUE;
1463 } else if (*haystack == '\"') {
1464 if (in_dquote)
1465 in_dquote = FALSE;
1466 else if (!in_squote)
1467 in_dquote = TRUE;
1468 }
1469
1470 haystack++;
1471 haystack_len--;
1472 }
1473
1474 return NULL;
1475}
1476
1477gchar *strcasestr_with_skip_quote(const gchar *haystack, const gchar *needle)
1478{
1479 register guint haystack_len, needle_len;
1480 gboolean in_squote = FALSE, in_dquote = FALSE;
1481
1482 haystack_len = strlen(haystack);
1483 needle_len = strlen(needle);
1484
1485 if (haystack_len < needle_len || needle_len == 0)
1486 return NULL;
1487
1488 while (haystack_len >= needle_len) {
1489 if (!in_squote && !in_dquote &&
1490 !g_ascii_strncasecmp(haystack, needle, needle_len))
1491 return (gchar *)haystack;
1492
1493 /* 'foo"bar"' -> foo"bar"
1494 "foo'bar'" -> foo'bar' */
1495 if (*haystack == '\'') {
1496 if (in_squote)
1497 in_squote = FALSE;
1498 else if (!in_dquote)
1499 in_squote = TRUE;
1500 } else if (*haystack == '\"') {
1501 if (in_dquote)
1502 in_dquote = FALSE;
1503 else if (!in_squote)
1504 in_dquote = TRUE;
1505 }
1506
1507 haystack++;
1508 haystack_len--;
1509 }
1510
1511 return NULL;
1512}
1513
1514gchar *strchr_parenthesis_close(const gchar *str, gchar op, gchar cl)
1515{
1516 const gchar *p;
1517 gchar quote_chr = '"';
1518 gint in_brace;
1519 gboolean in_quote = FALSE;
1520
1521 p = str;
1522
1523 if ((p = strchr_with_skip_quote(p, quote_chr, op))) {
1524 p++;
1525 in_brace = 1;
1526 while (*p) {
1527 if (*p == op && !in_quote)
1528 in_brace++;
1529 else if (*p == cl && !in_quote)
1530 in_brace--;
1531 else if (*p == quote_chr)
1532 in_quote ^= TRUE;
1533
1534 if (in_brace == 0)
1535 return (gchar *)p;
1536
1537 p++;
1538 }
1539 }
1540
1541 return NULL;
1542}
1543
1544gchar **strsplit_parenthesis(const gchar *str, gchar op, gchar cl,
1545 gint max_tokens)
1546{
1547 GSList *string_list = NULL, *slist;
1548 gchar **str_array;
1549 const gchar *s_op, *s_cl;
1550 guint i, n = 1;
1551
1552 g_return_val_if_fail(str != NULL, NULL);
1553
1554 if (max_tokens < 1)
1555 max_tokens = G_MAXINT;
1556
1557 s_op = strchr_with_skip_quote(str, '"', op);
1558 if (!s_op) return NULL;
1559 str = s_op;
1560 s_cl = strchr_parenthesis_close(str, op, cl);
1561 if (s_cl) {
1562 do {
1563 guint len;
1564 gchar *new_string;
1565
1566 str++;
1567 len = s_cl - str;
1568 new_string = g_new(gchar, len + 1);
1569 strncpy(new_string, str, len);
1570 new_string[len] = 0;
1571 string_list = g_slist_prepend(string_list, new_string);
1572 n++;
1573 str = s_cl + 1;
1574
1575 while (*str && g_ascii_isspace(*str)) str++;
1576 if (*str != op) {
1577 string_list = g_slist_prepend(string_list,
1578 g_strdup(""));
1579 n++;
1580 s_op = strchr_with_skip_quote(str, '"', op);
1581 if (!--max_tokens || !s_op) break;
1582 str = s_op;
1583 } else
1584 s_op = str;
1585 s_cl = strchr_parenthesis_close(str, op, cl);
1586 } while (--max_tokens && s_cl);
1587 }
1588
1589 str_array = g_new(gchar*, n);
1590
1591 i = n - 1;
1592 str_array[i--] = NULL;
1593 for (slist = string_list; slist; slist = slist->next)
1594 str_array[i--] = slist->data;
1595
1596 g_slist_free(string_list);
1597
1598 return str_array;
1599}
1600
1601gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1602 gint max_tokens)
1603{
1604 GSList *string_list = NULL, *slist;
1605 gchar **str_array, *s, *new_str;
1606 guint i, n = 1, len;
1607
1608 g_return_val_if_fail(str != NULL, NULL);
1609 g_return_val_if_fail(delim != NULL, NULL);
1610
1611 if (max_tokens < 1)
1612 max_tokens = G_MAXINT;
1613
1614 s = strstr_with_skip_quote(str, delim);
1615 if (s) {
1616 guint delimiter_len = strlen(delim);
1617
1618 do {
1619 len = s - str;
1620 new_str = g_strndup(str, len);
1621
1622 if (new_str[0] == '\'' || new_str[0] == '\"') {
1623 if (new_str[len - 1] == new_str[0]) {
1624 new_str[len - 1] = '\0';
1625 memmove(new_str, new_str + 1, len - 1);
1626 }
1627 }
1628 string_list = g_slist_prepend(string_list, new_str);
1629 n++;
1630 str = s + delimiter_len;
1631 s = strstr_with_skip_quote(str, delim);
1632 } while (--max_tokens && s);
1633 }
1634
1635 if (*str) {
1636 new_str = g_strdup(str);
1637 if (new_str[0] == '\'' || new_str[0] == '\"') {
1638 len = strlen(str);
1639 if (new_str[len - 1] == new_str[0]) {
1640 new_str[len - 1] = '\0';
1641 memmove(new_str, new_str + 1, len - 1);
1642 }
1643 }
1644 string_list = g_slist_prepend(string_list, new_str);
1645 n++;
1646 }
1647
1648 str_array = g_new(gchar*, n);
1649
1650 i = n - 1;
1651 str_array[i--] = NULL;
1652 for (slist = string_list; slist; slist = slist->next)
1653 str_array[i--] = slist->data;
1654
1655 g_slist_free(string_list);
1656
1657 return str_array;
1658}
1659
1660gchar **strsplit_csv(const gchar *str, gchar delim, gint max_tokens)
1661{
1662 GSList *string_list = NULL, *slist;
1663 gchar **str_array, *s, *new_str;
1664 gchar *tmp, *tmpp, *p;
1665 guint i, n = 1, len;
1666
1667 g_return_val_if_fail(str != NULL, NULL);
1668
1669 if (max_tokens < 1)
1670 max_tokens = G_MAXINT;
1671
1672 s = strchr_with_skip_quote(str, '"', delim);
1673 if (s) {
1674 do {
1675 len = s - str;
1676 tmpp = tmp = g_strndup(str, len);
1677
1678 if (tmp[0] == '"' && tmp[len - 1] == tmp[0]) {
1679 tmp[len - 1] = '\0';
1680 ++tmpp;
1681 p = new_str = g_malloc(len - 1);
1682 while (*tmpp) {
1683 if (*tmpp == '"' && *(tmpp + 1) == '"')
1684 ++tmpp;
1685 *p++ = *tmpp++;
1686 }
1687 *p = '\0';
1688 g_free(tmp);
1689 } else
1690 new_str = tmp;
1691
1692 string_list = g_slist_prepend(string_list, new_str);
1693 n++;
1694 str = s + 1;
1695 s = strchr_with_skip_quote(str, '"', delim);
1696 } while (--max_tokens && s);
1697 }
1698
1699 if (*str) {
1700 len = strlen(str);
1701 tmpp = tmp = g_strdup(str);
1702
1703 if (tmp[0] == '"' && tmp[len - 1] == tmp[0]) {
1704 tmp[len - 1] = '\0';
1705 ++tmpp;
1706 p = new_str = g_malloc(len - 1);
1707 while (*tmpp) {
1708 if (*tmpp == '"' && *(tmpp + 1) == '"')
1709 ++tmpp;
1710 *p++ = *tmpp++;
1711 }
1712 *p = '\0';
1713 g_free(tmp);
1714 } else
1715 new_str = tmp;
1716
1717 string_list = g_slist_prepend(string_list, new_str);
1718 n++;
1719 }
1720
1721 str_array = g_new(gchar*, n);
1722
1723 i = n - 1;
1724 str_array[i--] = NULL;
1725 for (slist = string_list; slist; slist = slist->next)
1726 str_array[i--] = slist->data;
1727
1728 g_slist_free(string_list);
1729
1730 return str_array;
1731}
1732
1733/* Concatenate multiple strings into a CSV (TSV) record.
1734 * If delimiter characters or double-quotes appear in the string,
1735 it is enclosed by double quotes.
1736 * Double quote characters are escaped by another double quote.
1737 * Strings must not include line breaks in this implementation.
1738 */
1739gchar *strconcat_csv(gchar delim, const gchar *field1, ...)
1740{
1741 va_list args;
1742 gchar *s;
1743 GString *csv;
1744 gint count = 0;
1745 const gchar *p;
1746
1747 g_return_val_if_fail(field1 != NULL, NULL);
1748
1749 csv = g_string_new("");
1750
1751 va_start(args, field1);
1752 s = (gchar *)field1;
1753 while (s) {
1754 gboolean add_quote = FALSE;
1755
1756 if (count > 0)
1757 g_string_append_c(csv, delim);
1758 if (strchr(s, delim) != NULL || strchr(s, '"') != NULL)
1759 add_quote = TRUE;
1760 if (add_quote)
1761 g_string_append_c(csv, '"');
1762 for (p = s; *p != '\0'; p++) {
1763 if (*p == '"')
1764 g_string_append_c(csv, '"');
1765 g_string_append_c(csv, *p);
1766 }
1767 if (add_quote)
1768 g_string_append_c(csv, '"');
1769 count++;
1770 s = va_arg(args, gchar*);
1771 }
1772 va_end(args);
1773
1774 return g_string_free(csv, FALSE);
1775}
1776
1777gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1778{
1779 gchar *abbrev_group;
1780 gchar *ap;
1781 const gchar *p = group;
1782 const gchar *last;
1783
1784 last = group + strlen(group);
1785 abbrev_group = ap = g_malloc(strlen(group) + 1);
1786
1787 while (*p) {
1788 while (*p == '.')
1789 *ap++ = *p++;
1790 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1791 *ap++ = *p++;
1792 while (*p != '.') p++;
1793 } else {
1794 strcpy(ap, p);
1795 return abbrev_group;
1796 }
1797 }
1798
1799 *ap = '\0';
1800 return abbrev_group;
1801}
1802
1803gchar *trim_string(const gchar *str, gint len)
1804{
1805 const gchar *p = str;
1806 gint mb_len;
1807 gchar *new_str;
1808 gint new_len = 0;
1809
1810 if (!str) return NULL;
1811 if (strlen(str) <= len)
1812 return g_strdup(str);
1813 if (g_utf8_validate(str, -1, NULL) == FALSE)
1814 return g_strdup(str);
1815
1816 while (*p != '\0') {
1817 mb_len = g_utf8_skip[*(guchar *)p];
1818 if (mb_len == 0)
1819 break;
1820 else if (new_len + mb_len > len)
1821 break;
1822
1823 new_len += mb_len;
1824 p += mb_len;
1825 }
1826
1827 Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1828 return g_strconcat(new_str, "...", NULL);
1829}
1830
1831gchar *trim_string_before(const gchar *str, gint len)
1832{
1833 const gchar *p = str;
1834 gint mb_len;
1835 gint new_len;
1836
1837 if (!str) return NULL;
1838 if ((new_len = strlen(str)) <= len)
1839 return g_strdup(str);
1840 if (g_utf8_validate(str, -1, NULL) == FALSE)
1841 return g_strdup(str);
1842
1843 while (*p != '\0') {
1844 mb_len = g_utf8_skip[*(guchar *)p];
1845 if (mb_len == 0)
1846 break;
1847
1848 new_len -= mb_len;
1849 p += mb_len;
1850
1851 if (new_len <= len)
1852 break;
1853 }
1854
1855 return g_strconcat("...", p, NULL);
1856}
1857
1858GList *uri_list_extract_filenames(const gchar *uri_list)
1859{
1860 GList *result = NULL;
1861 gchar *file;
1862
1863#if GLIB_CHECK_VERSION(2, 6, 0)
1864 gchar **uris;
1865 gint i;
1866
1867 uris = g_uri_list_extract_uris(uri_list);
1868 g_return_val_if_fail(uris != NULL, NULL);
1869
1870 for (i = 0; uris[i] != NULL; i++) {
1871 file = g_filename_from_uri(uris[i], NULL, NULL);
1872 if (file)
1873 result = g_list_append(result, file);
1874 }
1875
1876 g_strfreev(uris);
1877
1878 return result;
1879#else
1880 const gchar *p, *q;
1881
1882 p = uri_list;
1883
1884 while (p) {
1885 if (*p != '#') {
1886 while (g_ascii_isspace(*p)) p++;
1887 if (!strncmp(p, "file:", 5)) {
1888 p += 5;
1889 while (*p == '/' && *(p + 1) == '/') p++;
1890 q = p;
1891 while (*q && *q != '\n' && *q != '\r') q++;
1892
1893 if (q > p) {
1894 q--;
1895 while (q > p && g_ascii_isspace(*q))
1896 q--;
1897 file = g_malloc(q - p + 2);
1898 strncpy(file, p, q - p + 1);
1899 file[q - p + 1] = '\0';
1900 decode_uri(file, file);
1901 result = g_list_append(result, file);
1902 }
1903 }
1904 }
1905 p = strchr(p, '\n');
1906 if (p) p++;
1907 }
1908
1909 return result;
1910#endif
1911}
1912
1913#define HEX_TO_INT(val, hex) \
1914{ \
1915 gchar c = hex; \
1916 \
1917 if ('0' <= c && c <= '9') { \
1918 val = c - '0'; \
1919 } else if ('a' <= c && c <= 'f') { \
1920 val = c - 'a' + 10; \
1921 } else if ('A' <= c && c <= 'F') { \
1922 val = c - 'A' + 10; \
1923 } else { \
1924 val = 0; \
1925 } \
1926}
1927
1928#define INT_TO_HEX(hex, val) \
1929{ \
1930 if ((val) < 10) \
1931 hex = '0' + (val); \
1932 else \
1933 hex = 'a' + (val) - 10; \
1934}
1935
1936/* Converts two-digit hexadecimal to decimal. Used for unescaping escaped
1937 * characters.
1938 */
1939static gint axtoi(const gchar *hex_str)
1940{
1941 gint hi, lo;
1942
1943 HEX_TO_INT(hi, hex_str[0]);
1944 HEX_TO_INT(lo, hex_str[1]);
1945
1946 return (hi << 4) + lo;
1947}
1948
1949static void get_hex_str(gchar *out, guchar ch)
1950{
1951 gchar hex;
1952
1953 INT_TO_HEX(hex, ch >> 4);
1954 *out++ = hex;
1955 INT_TO_HEX(hex, ch & 0x0f);
1956 *out++ = hex;
1957}
1958
1959gboolean is_uri_string(const gchar *str)
1960{
1961 return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1962 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1963 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1964 g_ascii_strncasecmp(str, "www.", 4) == 0);
1965}
1966
1967gchar *get_uri_path(const gchar *uri)
1968{
1969 if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1970 return (gchar *)(uri + 7);
1971 else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1972 return (gchar *)(uri + 8);
1973 else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1974 return (gchar *)(uri + 6);
1975 else
1976 return (gchar *)uri;
1977}
1978
1979gint get_uri_len(const gchar *str)
1980{
1981 const gchar *p;
1982
1983 if (is_uri_string(str)) {
1984 for (p = str; *p != '\0'; p++) {
1985 if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1986 break;
1987 }
1988 return p - str;
1989 }
1990
1991 return 0;
1992}
1993
1994/* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1995 * plusses, and escape characters are used)
1996 * Note: decoded_uri and encoded_uri can point the same location
1997 */
1998void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1999{
2000 gchar *dec = decoded_uri;
2001 const gchar *enc = encoded_uri;
2002
2003 while (*enc) {
2004 if (*enc == '%') {
2005 enc++;
2006 if (g_ascii_isxdigit((guchar)enc[0]) &&
2007 g_ascii_isxdigit((guchar)enc[1])) {
2008 *dec = axtoi(enc);
2009 dec++;
2010 enc += 2;
2011 }
2012 } else {
2013 if (*enc == '+')
2014 *dec = ' ';
2015 else
2016 *dec = *enc;
2017 dec++;
2018 enc++;
2019 }
2020 }
2021
2022 *dec = '\0';
2023}
2024
2025void decode_xdigit_encoded_str(gchar *decoded, const gchar *encoded)
2026{
2027 gchar *dec = decoded;
2028 const gchar *enc = encoded;
2029
2030 while (*enc) {
2031 if (*enc == '%') {
2032 enc++;
2033 if (g_ascii_isxdigit((guchar)enc[0]) &&
2034 g_ascii_isxdigit((guchar)enc[1])) {
2035 *dec++ = axtoi(enc);
2036 enc += 2;
2037 }
2038 } else
2039 *dec++ = *enc++;
2040 }
2041
2042 *dec = '\0';
2043}
2044
2045gchar *encode_uri(const gchar *filename)
2046{
2047 gchar *uri;
2048
2049 uri = g_filename_to_uri(filename, NULL, NULL);
2050 if (!uri)
2051 uri = g_strconcat("file://", filename, NULL);
2052
2053 return uri;
2054}
2055
2056gchar *uriencode_for_filename(const gchar *filename)
2057{
2058 const gchar *p = filename;
2059 gchar *enc, *outp;
2060
2061 outp = enc = g_malloc(strlen(filename) * 3 + 1);
2062
2063 for (p = filename; *p != '\0'; p++) {
2064 if (strchr("\t\r\n\"'\\/:;*?<>|", *p)) {
2065 *outp++ = '%';
2066 get_hex_str(outp, *p);
2067 outp += 2;
2068 } else
2069 *outp++ = *p;
2070 }
2071
2072 *outp = '\0';
2073 return enc;
2074}
2075
2076gchar *uriencode_for_mailto(const gchar *mailto)
2077{
2078 const gchar *p = mailto;
2079 gchar *enc, *outp;
2080
2081 outp = enc = g_malloc(strlen(mailto) * 3 + 1);
2082
2083 for (p = mailto; *p != '\0'; p++) {
2084 if (*p == '+') {
2085 *outp++ = '%';
2086 get_hex_str(outp, *p);
2087 outp += 2;
2088 } else
2089 *outp++ = *p;
2090 }
2091
2092 *outp = '\0';
2093 return enc;
2094}
2095
2096gint scan_mailto_url(const gchar *mailto, gchar **to, gchar **cc, gchar **bcc,
2097 gchar **subject, gchar **inreplyto, gchar **body)
2098{
2099 gchar *tmp_mailto;
2100 gchar *p;
2101
2102 Xstrdup_a(tmp_mailto, mailto, return -1);
2103
2104 if (!strncmp(tmp_mailto, "mailto:", 7))
2105 tmp_mailto += 7;
2106
2107 p = strchr(tmp_mailto, '?');
2108 if (p) {
2109 *p = '\0';
2110 p++;
2111 }
2112
2113 if (to && !*to) {
2114 *to = g_malloc(strlen(tmp_mailto) + 1);
2115 decode_uri(*to, tmp_mailto);
2116 }
2117
2118 while (p) {
2119 gchar *field, *value;
2120
2121 field = p;
2122
2123 p = strchr(p, '=');
2124 if (!p) break;
2125 *p = '\0';
2126 p++;
2127
2128 value = p;
2129
2130 p = strchr(p, '&');
2131 if (p) {
2132 *p = '\0';
2133 p++;
2134 }
2135
2136 if (*value == '\0') continue;
2137
2138 if (cc && !*cc && !g_ascii_strcasecmp(field, "cc")) {
2139 *cc = g_malloc(strlen(value) + 1);
2140 decode_uri(*cc, value);
2141 } else if (bcc && !*bcc && !g_ascii_strcasecmp(field, "bcc")) {
2142 *bcc = g_malloc(strlen(value) + 1);
2143 decode_uri(*bcc, value);
2144 } else if (subject && !*subject &&
2145 !g_ascii_strcasecmp(field, "subject")) {
2146 *subject = g_malloc(strlen(value) + 1);
2147 decode_uri(*subject, value);
2148 } else if (inreplyto && !*inreplyto &&
2149 !g_ascii_strcasecmp(field, "in-reply-to")) {
2150 *inreplyto = g_malloc(strlen(value) + 1);
2151 decode_uri(*inreplyto, value);
2152 } else if (body && !*body &&
2153 !g_ascii_strcasecmp(field, "body")) {
2154 *body = g_malloc(strlen(value) + 1);
2155 decode_uri(*body, value);
2156 }
2157 }
2158
2159 return 0;
2160}
2161
2162static gchar *startup_dir = NULL;
2163static gchar *rc_dir = NULL;
2164
2165void set_startup_dir(void)
2166{
2167#ifdef G_OS_WIN32
2168 if (!startup_dir) {
2169 startup_dir = g_win32_get_package_installation_directory
2170 (NULL, NULL);
2171 if (startup_dir) {
2172 if (g_chdir(startup_dir) < 0) {
2173 FILE_OP_ERROR(startup_dir, "chdir");
2174 g_free(startup_dir);
2175 startup_dir = g_get_current_dir();
2176 }
2177 } else
2178 startup_dir = g_get_current_dir();
2179 }
2180#elif defined(__APPLE__)
2181 if (!startup_dir) {
2182 const gchar *path;
2183 NSAutoreleasePool *pool;
2184
2185 pool = [[NSAutoreleasePool alloc] init];
2186
2187 path = [[[NSBundle mainBundle] bundlePath] UTF8String];
2188 startup_dir = g_strdup(path);
2189
2190 [pool release];
2191
2192 if (!startup_dir)
2193 startup_dir = g_get_current_dir();
2194 }
2195#else
2196 if (!startup_dir)
2197 startup_dir = g_get_current_dir();
2198#endif
2199}
2200
2201void set_rc_dir(const gchar *dir)
2202{
2203 if (rc_dir)
2204 g_free(rc_dir);
2205
2206 if (dir) {
2207 if (g_path_is_absolute(dir))
2208 rc_dir = g_strdup(dir);
2209 else
2210 rc_dir = g_strconcat(get_startup_dir(),
2211 G_DIR_SEPARATOR_S, dir, NULL);
2212 } else
2213 rc_dir = NULL;
2214}
2215
2216const gchar *get_startup_dir(void)
2217{
2218 if (!startup_dir)
2219 set_startup_dir();
2220
2221 return startup_dir;
2222}
2223
2224#ifdef G_OS_WIN32
2225static gchar *get_win32_special_folder_path(gint nfolder)
2226{
2227 gchar *folder = NULL;
2228 HRESULT hr;
2229
2230 if (G_WIN32_HAVE_WIDECHAR_API()) {
2231 wchar_t path[MAX_PATH + 1];
2232 hr = SHGetFolderPathW(NULL, nfolder, NULL, 0, path);
2233 if (hr == S_OK)
2234 folder = g_utf16_to_utf8(path, -1, NULL, NULL, NULL);
2235 } else {
2236 gchar path[MAX_PATH + 1];
2237 hr = SHGetFolderPathA(NULL, nfolder, NULL, 0, path);
2238 if (hr == S_OK)
2239 folder = g_locale_to_utf8(path, -1, NULL, NULL, NULL);
2240 }
2241
2242 return folder;
2243}
2244#endif
2245
2246const gchar *get_home_dir(void)
2247{
2248#ifdef G_OS_WIN32
2249 static const gchar *home_dir = NULL;
2250
2251 if (!home_dir) {
2252 home_dir = g_get_home_dir();
2253 if (!home_dir)
2254 home_dir = "C:\\Sylpheed";
2255 }
2256
2257 return home_dir;
2258#else
2259 return g_get_home_dir();
2260#endif
2261}
2262
2263const gchar *get_document_dir(void)
2264{
2265#ifdef G_OS_WIN32
2266 static const gchar *document_dir = NULL;
2267 HRESULT hr;
2268
2269 if (!document_dir) {
2270 document_dir = get_win32_special_folder_path(CSIDL_PERSONAL);
2271 if (!document_dir)
2272 document_dir = get_home_dir();
2273 }
2274
2275 return document_dir;
2276#elif defined(__APPLE__)
2277 static const gchar *document_dir = NULL;
2278
2279 if (!document_dir) {
2280 document_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
2281 "Documents", NULL);
2282 }
2283
2284 return document_dir;
2285#else
2286 return get_home_dir();
2287#endif
2288}
2289
2290const gchar *get_rc_dir(void)
2291{
2292 if (!rc_dir) {
2293#ifdef G_OS_WIN32
2294 gchar *appdata;
2295
2296 appdata = get_win32_special_folder_path(CSIDL_APPDATA);
2297 if (appdata)
2298 rc_dir = g_strconcat(appdata, G_DIR_SEPARATOR_S,
2299 RC_DIR, NULL);
2300 else
2301 rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
2302 RC_DIR, NULL);
2303 g_free(appdata);
2304#elif defined(__APPLE__)
2305 rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
2306 "Library", G_DIR_SEPARATOR_S,
2307 "Application Support", G_DIR_SEPARATOR_S,
2308 RC_DIR, NULL);
2309#else
2310 rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
2311 RC_DIR, NULL);
2312#endif
2313 }
2314
2315 return rc_dir;
2316}
2317
2318const gchar *get_old_rc_dir(void)
2319{
2320 static gchar *old_rc_dir = NULL;
2321
2322 if (!old_rc_dir)
2323 old_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
2324 OLD_RC_DIR, NULL);
2325
2326 return old_rc_dir;
2327}
2328
2329const gchar *get_mail_base_dir(void)
2330{
2331#if defined(G_OS_WIN32) || defined(__APPLE__)
2332 static gchar *mail_base_dir = NULL;
2333
2334 if (!mail_base_dir)
2335 mail_base_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2336 "Mailboxes", NULL);
2337
2338 return mail_base_dir;
2339#else
2340 return get_home_dir();
2341#endif
2342}
2343
2344const gchar *get_news_cache_dir(void)
2345{
2346 static gchar *news_cache_dir = NULL;
2347
2348 if (!news_cache_dir)
2349 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2350 NEWS_CACHE_DIR, NULL);
2351
2352 return news_cache_dir;
2353}
2354
2355const gchar *get_imap_cache_dir(void)
2356{
2357 static gchar *imap_cache_dir = NULL;
2358
2359 if (!imap_cache_dir)
2360 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2361 IMAP_CACHE_DIR, NULL);
2362
2363 return imap_cache_dir;
2364}
2365
2366const gchar *get_mime_tmp_dir(void)
2367{
2368 static gchar *mime_tmp_dir = NULL;
2369
2370 if (!mime_tmp_dir)
2371 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2372 MIME_TMP_DIR, NULL);
2373
2374 return mime_tmp_dir;
2375}
2376
2377const gchar *get_template_dir(void)
2378{
2379 static gchar *template_dir = NULL;
2380
2381 if (!template_dir)
2382 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2383 TEMPLATE_DIR, NULL);
2384
2385 return template_dir;
2386}
2387
2388const gchar *get_tmp_dir(void)
2389{
2390 static gchar *tmp_dir = NULL;
2391
2392 if (!tmp_dir)
2393 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2394 TMP_DIR, NULL);
2395
2396 return tmp_dir;
2397}
2398
2399gchar *get_tmp_file(void)
2400{
2401 gchar *tmp_file;
2402 static guint32 id = 0;
2403
2404 tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
2405 get_tmp_dir(), G_DIR_SEPARATOR, id++);
2406
2407 return tmp_file;
2408}
2409
2410const gchar *get_domain_name(void)
2411{
2412 static gchar *domain_name = NULL;
2413
2414 if (!domain_name) {
2415 gchar buf[128] = "";
2416 struct hostent *hp;
2417
2418 if (gethostname(buf, sizeof(buf)) < 0) {
2419 perror("gethostname");
2420 domain_name = "unknown";
2421 } else {
2422 buf[sizeof(buf) - 1] = '\0';
2423 if ((hp = my_gethostbyname(buf)) == NULL) {
2424 perror("gethostbyname");
2425 domain_name = g_strdup(buf);
2426 } else {
2427 domain_name = g_strdup(hp->h_name);
2428 }
2429 }
2430
2431 debug_print("domain name = %s\n", domain_name);
2432 if (is_next_nonascii(domain_name)) {
2433 g_warning("invalid domain name: %s\n", domain_name);
2434 g_free(domain_name);
2435 domain_name = "unknown";
2436 }
2437 }
2438
2439 return domain_name;
2440}
2441
2442off_t get_file_size(const gchar *file)
2443{
2444 GStatBuf s;
2445
2446 if (g_stat(file, &s) < 0) {
2447 FILE_OP_ERROR(file, "stat");
2448 return -1;
2449 }
2450
2451 return s.st_size;
2452}
2453
2454off_t get_file_size_as_crlf(const gchar *file)
2455{
2456 FILE *fp;
2457 off_t size = 0;
2458 gchar buf[BUFFSIZE];
2459
2460 if ((fp = g_fopen(file, "rb")) == NULL) {
2461 FILE_OP_ERROR(file, "fopen");
2462 return -1;
2463 }
2464
2465 while (fgets(buf, sizeof(buf), fp) != NULL) {
2466 strretchomp(buf);
2467 size += strlen(buf) + 2;
2468 }
2469
2470 if (ferror(fp)) {
2471 FILE_OP_ERROR(file, "fgets");
2472 size = -1;
2473 }
2474
2475 fclose(fp);
2476
2477 return size;
2478}
2479
2480off_t get_left_file_size(FILE *fp)
2481{
2482 glong pos;
2483 glong end;
2484 off_t size;
2485
2486 if ((pos = ftell(fp)) < 0) {
2487 perror("ftell");
2488 return -1;
2489 }
2490 if (fseek(fp, 0L, SEEK_END) < 0) {
2491 perror("fseek");
2492 return -1;
2493 }
2494 if ((end = ftell(fp)) < 0) {
2495 perror("fseek");
2496 return -1;
2497 }
2498 size = end - pos;
2499 if (fseek(fp, pos, SEEK_SET) < 0) {
2500 perror("fseek");
2501 return -1;
2502 }
2503
2504 return size;
2505}
2506
2507gint get_last_empty_line_size(FILE *fp, off_t size)
2508{
2509 glong pos;
2510 gint lsize = 0;
2511 gchar buf[4];
2512 size_t nread;
2513
2514 if (size < 4)
2515 return -1;
2516
2517 if ((pos = ftell(fp)) < 0) {
2518 perror("ftell");
2519 return -1;
2520 }
2521 if (fseek(fp, size - 4, SEEK_CUR) < 0) {
2522 perror("fseek");
2523 return -1;
2524 }
2525
2526 /* read last 4 bytes */
2527 nread = fread(buf, sizeof(buf), 1, fp);
2528 if (nread != 1) {
2529 perror("fread");
2530 return -1;
2531 }
2532 /* g_print("last 4 bytes: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]); */
2533 if (buf[3] == '\n') {
2534 if (buf[2] == '\n')
2535 lsize = 1;
2536 else if (buf[2] == '\r') {
2537 if (buf[1] == '\n')
2538 lsize = 2;
2539 }
2540 }
2541
2542 if (fseek(fp, pos, SEEK_SET) < 0) {
2543 perror("fseek");
2544 return -1;
2545 }
2546
2547 return lsize;
2548}
2549
2550gboolean file_exist(const gchar *file, gboolean allow_fifo)
2551{
2552 if (file == NULL)
2553 return FALSE;
2554
2555 if (allow_fifo) {
2556 GStatBuf s;
2557
2558 if (g_stat(file, &s) < 0) {
2559 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2560 return FALSE;
2561 }
2562 if (S_ISREG(s.st_mode) || S_ISFIFO(s.st_mode))
2563 return TRUE;
2564 } else {
2565 return g_file_test(file, G_FILE_TEST_IS_REGULAR);
2566 }
2567
2568 return FALSE;
2569}
2570
2571gboolean is_dir_exist(const gchar *dir)
2572{
2573 if (dir == NULL)
2574 return FALSE;
2575
2576 return g_file_test(dir, G_FILE_TEST_IS_DIR);
2577}
2578
2579gboolean is_file_entry_exist(const gchar *file)
2580{
2581 if (file == NULL)
2582 return FALSE;
2583
2584 return g_file_test(file, G_FILE_TEST_EXISTS);
2585}
2586
2587gboolean dirent_is_regular_file(struct dirent *d)
2588{
2589#ifdef HAVE_DIRENT_D_TYPE
2590 if (d->d_type == DT_REG)
2591 return TRUE;
2592 else if (d->d_type != DT_UNKNOWN)
2593 return FALSE;
2594#endif
2595
2596 return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2597}
2598
2599gboolean dirent_is_directory(struct dirent *d)
2600{
2601#ifdef HAVE_DIRENT_D_TYPE
2602 if (d->d_type == DT_DIR)
2603 return TRUE;
2604 else if (d->d_type != DT_UNKNOWN)
2605 return FALSE;
2606#endif
2607
2608 return g_file_test(d->d_name, G_FILE_TEST_IS_DIR);
2609}
2610
2611gint change_dir(const gchar *dir)
2612{
2613 gchar *prevdir = NULL;
2614
2615 if (debug_mode)
2616 prevdir = g_get_current_dir();
2617
2618 if (g_chdir(dir) < 0) {
2619 FILE_OP_ERROR(dir, "chdir");
2620 if (debug_mode) g_free(prevdir);
2621 return -1;
2622 } else if (debug_mode) {
2623 gchar *cwd;
2624
2625 cwd = g_get_current_dir();
2626 if (strcmp(prevdir, cwd) != 0)
2627 g_print("current dir: %s\n", cwd);
2628 g_free(cwd);
2629 g_free(prevdir);
2630 }
2631
2632 return 0;
2633}
2634
2635gint make_dir(const gchar *dir)
2636{
2637 if (g_mkdir(dir, S_IRWXU) < 0) {
2638 FILE_OP_ERROR(dir, "mkdir");
2639 return -1;
2640 }
2641 if (g_chmod(dir, S_IRWXU) < 0)
2642 FILE_OP_ERROR(dir, "chmod");
2643
2644 return 0;
2645}
2646
2647gint make_dir_hier(const gchar *dir)
2648{
2649 gchar *parent_dir;
2650 const gchar *p;
2651
2652 for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2653 parent_dir = g_strndup(dir, p - dir);
2654 if (*parent_dir != '\0') {
2655 if (!is_dir_exist(parent_dir)) {
2656 if (make_dir(parent_dir) < 0) {
2657 g_free(parent_dir);
2658 return -1;
2659 }
2660 }
2661 }
2662 g_free(parent_dir);
2663 }
2664
2665 if (!is_dir_exist(dir)) {
2666 if (make_dir(dir) < 0)
2667 return -1;
2668 }
2669
2670 return 0;
2671}
2672
2673gint remove_all_files(const gchar *dir)
2674{
2675 GDir *dp;
2676 const gchar *dir_name;
2677 gchar *prev_dir;
2678
2679 prev_dir = g_get_current_dir();
2680
2681 if (g_chdir(dir) < 0) {
2682 FILE_OP_ERROR(dir, "chdir");
2683 g_free(prev_dir);
2684 return -1;
2685 }
2686
2687 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2688 g_warning("failed to open directory: %s\n", dir);
2689 g_free(prev_dir);
2690 return -1;
2691 }
2692
2693 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2694 if (g_unlink(dir_name) < 0)
2695 FILE_OP_ERROR(dir_name, "unlink");
2696 }
2697
2698 g_dir_close(dp);
2699
2700 if (g_chdir(prev_dir) < 0) {
2701 FILE_OP_ERROR(prev_dir, "chdir");
2702 g_free(prev_dir);
2703 return -1;
2704 }
2705
2706 g_free(prev_dir);
2707
2708 return 0;
2709}
2710
2711gint remove_numbered_files(const gchar *dir, guint first, guint last)
2712{
2713 GDir *dp;
2714 const gchar *dir_name;
2715 gchar *prev_dir;
2716 guint file_no;
2717
2718 prev_dir = g_get_current_dir();
2719
2720 if (g_chdir(dir) < 0) {
2721 FILE_OP_ERROR(dir, "chdir");
2722 g_free(prev_dir);
2723 return -1;
2724 }
2725
2726 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2727 g_warning("failed to open directory: %s\n", dir);
2728 g_free(prev_dir);
2729 return -1;
2730 }
2731
2732 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2733 file_no = to_unumber(dir_name);
2734 if (file_no > 0 && first <= file_no && file_no <= last) {
2735 if (is_dir_exist(dir_name))
2736 continue;
2737 if (g_unlink(dir_name) < 0)
2738 FILE_OP_ERROR(dir_name, "unlink");
2739 }
2740 }
2741
2742 g_dir_close(dp);
2743
2744 if (g_chdir(prev_dir) < 0) {
2745 FILE_OP_ERROR(prev_dir, "chdir");
2746 g_free(prev_dir);
2747 return -1;
2748 }
2749
2750 g_free(prev_dir);
2751
2752 return 0;
2753}
2754
2755gint remove_all_numbered_files(const gchar *dir)
2756{
2757 return remove_numbered_files(dir, 0, UINT_MAX);
2758}
2759
2760gint remove_expired_files(const gchar *dir, guint hours)
2761{
2762 GDir *dp;
2763 const gchar *dir_name;
2764 GStatBuf s;
2765 gchar *prev_dir;
2766 guint file_no;
2767 time_t mtime, now, expire_time;
2768
2769 prev_dir = g_get_current_dir();
2770
2771 if (g_chdir(dir) < 0) {
2772 FILE_OP_ERROR(dir, "chdir");
2773 g_free(prev_dir);
2774 return -1;
2775 }
2776
2777 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2778 g_warning("failed to open directory: %s\n", dir);
2779 g_free(prev_dir);
2780 return -1;
2781 }
2782
2783 now = time(NULL);
2784 expire_time = hours * 60 * 60;
2785
2786 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2787 file_no = to_unumber(dir_name);
2788 if (file_no > 0) {
2789 if (g_stat(dir_name, &s) < 0) {
2790 FILE_OP_ERROR(dir_name, "stat");
2791 continue;
2792 }
2793 if (S_ISDIR(s.st_mode))
2794 continue;
2795 mtime = MAX(s.st_mtime, s.st_atime);
2796 if (now - mtime > expire_time) {
2797 if (g_unlink(dir_name) < 0)
2798 FILE_OP_ERROR(dir_name, "unlink");
2799 }
2800 }
2801 }
2802
2803 g_dir_close(dp);
2804
2805 if (g_chdir(prev_dir) < 0) {
2806 FILE_OP_ERROR(prev_dir, "chdir");
2807 g_free(prev_dir);
2808 return -1;
2809 }
2810
2811 g_free(prev_dir);
2812
2813 return 0;
2814}
2815
2816static gint remove_dir_recursive_real(const gchar *dir)
2817{
2818 GStatBuf s;
2819 GDir *dp;
2820 const gchar *dir_name;
2821 gchar *prev_dir;
2822
2823 if (g_stat(dir, &s) < 0) {
2824 FILE_OP_ERROR(dir, "stat");
2825 if (ENOENT == errno) return 0;
2826 return -1;
2827 }
2828
2829 if (!S_ISDIR(s.st_mode)) {
2830 if (g_unlink(dir) < 0) {
2831 FILE_OP_ERROR(dir, "unlink");
2832 return -1;
2833 }
2834
2835 return 0;
2836 }
2837
2838 prev_dir = g_get_current_dir();
2839 /* g_print("prev_dir = %s\n", prev_dir); */
2840
2841 if (g_chdir(dir) < 0) {
2842 FILE_OP_ERROR(dir, "chdir");
2843 g_free(prev_dir);
2844 return -1;
2845 }
2846
2847 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2848 g_warning("failed to open directory: %s\n", dir);
2849 g_chdir(prev_dir);
2850 g_free(prev_dir);
2851 return -1;
2852 }
2853
2854 /* remove all files in the directory */
2855 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2856 /* g_print("removing %s\n", dir_name); */
2857
2858 if (is_dir_exist(dir_name)) {
2859 if (remove_dir_recursive_real(dir_name) < 0) {
2860 g_warning("can't remove directory\n");
2861 return -1;
2862 }
2863 } else {
2864 if (g_unlink(dir_name) < 0)
2865 FILE_OP_ERROR(dir_name, "unlink");
2866 }
2867 }
2868
2869 g_dir_close(dp);
2870
2871 if (g_chdir(prev_dir) < 0) {
2872 FILE_OP_ERROR(prev_dir, "chdir");
2873 g_free(prev_dir);
2874 return -1;
2875 }
2876
2877 g_free(prev_dir);
2878
2879 if (g_rmdir(dir) < 0) {
2880 if (ENOTDIR == errno) {
2881 if (g_unlink(dir) < 0) {
2882 FILE_OP_ERROR(dir, "unlink");
2883 return -1;
2884 }
2885 } else {
2886 FILE_OP_ERROR(dir, "rmdir");
2887 return -1;
2888 }
2889 }
2890
2891 return 0;
2892}
2893
2894gint remove_dir_recursive(const gchar *dir)
2895{
2896 gchar *cur_dir;
2897 gint ret;
2898
2899 debug_print("remove_dir_recursive: %s\n", dir);
2900
2901 cur_dir = g_get_current_dir();
2902
2903 if (g_chdir(dir) < 0) {
2904 FILE_OP_ERROR(dir, "chdir");
2905 ret = -1;
2906 goto leave;
2907 }
2908 if (g_chdir("..") < 0) {
2909 FILE_OP_ERROR(dir, "chdir");
2910 ret = -1;
2911 goto leave;
2912 }
2913
2914 ret = remove_dir_recursive_real(dir);
2915
2916leave:
2917 if (is_dir_exist(cur_dir)) {
2918 if (g_chdir(cur_dir) < 0) {
2919 FILE_OP_ERROR(cur_dir, "chdir");
2920 }
2921 }
2922
2923 g_free(cur_dir);
2924
2925 return ret;
2926}
2927
2928gint rename_force(const gchar *oldpath, const gchar *newpath)
2929{
2930#if !defined(G_OS_UNIX) && !GLIB_CHECK_VERSION(2, 9, 1)
2931 if (!is_file_entry_exist(oldpath)) {
2932 errno = ENOENT;
2933 return -1;
2934 }
2935 if (is_file_exist(newpath)) {
2936 if (g_unlink(newpath) < 0)
2937 FILE_OP_ERROR(newpath, "unlink");
2938 }
2939#endif
2940 return g_rename(oldpath, newpath);
2941}
2942
2943gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2944{
2945#ifdef G_OS_WIN32
2946 wchar_t *wsrc;
2947 wchar_t *wdest;
2948 gchar *dest_bak = NULL;
2949 gboolean err = FALSE;
2950
2951 wsrc = g_utf8_to_utf16(src, -1, NULL, NULL, NULL);
2952 if (wsrc == NULL) {
2953 return -1;
2954 }
2955 wdest = g_utf8_to_utf16(dest, -1, NULL, NULL, NULL);
2956 if (wdest == NULL) {
2957 g_free(wsrc);
2958 return -1;
2959 }
2960
2961 if (keep_backup == FALSE) {
2962 if (CopyFileW(wsrc, wdest, FALSE) == 0)
2963 err = TRUE;
2964 g_free(wdest);
2965 g_free(wsrc);
2966 return err ? -1 : 0;
2967 }
2968
2969 if (is_file_exist(dest)) {
2970 dest_bak = g_strconcat(dest, ".bak", NULL);
2971 if (rename_force(dest, dest_bak) < 0) {
2972 FILE_OP_ERROR(dest, "rename");
2973 g_free(dest_bak);
2974 g_free(wdest);
2975 g_free(wsrc);
2976 return -1;
2977 }
2978 }
2979
2980 if (CopyFileW(wsrc, wdest, FALSE) == 0)
2981 err = TRUE;
2982
2983 g_free(wdest);
2984 g_free(wsrc);
2985#else
2986 gint srcfd, destfd;
2987 gint n_read;
2988 gchar buf[BUFFSIZE];
2989 gchar *dest_bak = NULL;
2990 gboolean err = FALSE;
2991
2992 if ((srcfd = g_open(src, O_RDONLY, 0600)) < 0) {
2993 FILE_OP_ERROR(src, "open");
2994 return -1;
2995 }
2996 if (is_file_exist(dest)) {
2997 dest_bak = g_strconcat(dest, ".bak", NULL);
2998 if (rename_force(dest, dest_bak) < 0) {
2999 FILE_OP_ERROR(dest, "rename");
3000 close(srcfd);
3001 g_free(dest_bak);
3002 return -1;
3003 }
3004 }
3005
3006 if ((destfd = g_open(dest, O_WRONLY | O_CREAT, 0600)) < 0) {
3007 FILE_OP_ERROR(dest, "open");
3008 close(srcfd);
3009 if (dest_bak) {
3010 if (rename_force(dest_bak, dest) < 0)
3011 FILE_OP_ERROR(dest_bak, "rename");
3012 g_free(dest_bak);
3013 }
3014 return -1;
3015 }
3016
3017 while ((n_read = read(srcfd, buf, sizeof(buf))) > 0) {
3018 gchar *p = buf;
3019 const gchar *endp = buf + n_read;
3020 gint n_write;
3021
3022 while (p < endp) {
3023 if ((n_write = write(destfd, p, endp - p)) < 0) {
3024 g_warning(_("writing to %s failed.\n"), dest);
3025 close(destfd);
3026 close(srcfd);
3027 g_unlink(dest);
3028 if (dest_bak) {
3029 if (rename_force(dest_bak, dest) < 0)
3030 FILE_OP_ERROR(dest_bak, "rename");
3031 g_free(dest_bak);
3032 }
3033 return -1;
3034 }
3035 p += n_write;
3036 }
3037 }
3038
3039 if (close(destfd) < 0) {
3040 FILE_OP_ERROR(dest, "close");
3041 err = TRUE;
3042 }
3043 close(srcfd);
3044#endif
3045
3046 if (err) {
3047 g_unlink(dest);
3048 if (dest_bak) {
3049 if (rename_force(dest_bak, dest) < 0)
3050 FILE_OP_ERROR(dest_bak, "rename");
3051 g_free(dest_bak);
3052 }
3053 return -1;
3054 }
3055
3056 if (keep_backup == FALSE && dest_bak)
3057 g_unlink(dest_bak);
3058
3059 g_free(dest_bak);
3060
3061 return 0;
3062}
3063
3064gint copy_dir(const gchar *src, const gchar *dest)
3065{
3066 GDir *dir;
3067 const gchar *dir_name;
3068 gchar *src_file;
3069 gchar *dest_file;
3070
3071 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
3072 g_warning("failed to open directory: %s\n", src);
3073 return -1;
3074 }
3075
3076 if (make_dir_hier(dest) < 0) {
3077 g_dir_close(dir);
3078 return -1;
3079 }
3080
3081 while ((dir_name = g_dir_read_name(dir)) != NULL) {
3082 src_file = g_strconcat(src, G_DIR_SEPARATOR_S, dir_name, NULL);
3083 dest_file = g_strconcat(dest, G_DIR_SEPARATOR_S, dir_name,
3084 NULL);
3085 if (is_file_exist(src_file))
3086 copy_file(src_file, dest_file, FALSE);
3087 g_free(dest_file);
3088 g_free(src_file);
3089 }
3090
3091 g_dir_close(dir);
3092
3093 return 0;
3094}
3095
3096gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
3097{
3098 if (overwrite == FALSE && is_file_entry_exist(dest)) {
3099 g_warning("move_file(): file %s already exists.", dest);
3100 return -1;
3101 }
3102
3103 if (rename_force(src, dest) == 0) return 0;
3104
3105 if (EXDEV != errno) {
3106 FILE_OP_ERROR(src, "rename");
3107 return -1;
3108 }
3109
3110 if (copy_file(src, dest, FALSE) < 0) return -1;
3111
3112 g_unlink(src);
3113
3114 return 0;
3115}
3116
3117gint append_file_part(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
3118{
3119 gint n_read;
3120 gint bytes_left, to_read;
3121 gchar buf[BUFSIZ];
3122
3123 g_return_val_if_fail(fp != NULL, -1);
3124 g_return_val_if_fail(dest_fp != NULL, -1);
3125
3126 if (fseek(fp, offset, SEEK_SET) < 0) {
3127 perror("fseek");
3128 return -1;
3129 }
3130
3131 bytes_left = length;
3132 to_read = MIN(bytes_left, sizeof(buf));
3133
3134 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
3135 if (n_read < to_read && ferror(fp))
3136 break;
3137 if (fwrite(buf, n_read, 1, dest_fp) < 1) {
3138 g_warning("append_file_part: writing to file failed.\n");
3139 return -1;
3140 }
3141 bytes_left -= n_read;
3142 if (bytes_left == 0)
3143 break;
3144 to_read = MIN(bytes_left, sizeof(buf));
3145 }
3146
3147 if (ferror(fp)) {
3148 perror("fread");
3149 return -1;
3150 }
3151 if (fflush(dest_fp) == EOF) {
3152 FILE_OP_ERROR("append_file_part", "fflush");
3153 return -1;
3154 }
3155
3156 return 0;
3157}
3158
3159gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
3160{
3161 FILE *dest_fp;
3162
3163 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
3164 FILE_OP_ERROR(dest, "fopen");
3165 return -1;
3166 }
3167
3168 if (change_file_mode_rw(dest_fp, dest) < 0) {
3169 FILE_OP_ERROR(dest, "chmod");
3170 g_warning("can't change file mode\n");
3171 }
3172
3173 if (append_file_part(fp, offset, length, dest_fp) < 0) {
3174 g_warning("writing to %s failed.\n", dest);
3175 fclose(dest_fp);
3176 g_unlink(dest);
3177 return -1;
3178 }
3179
3180 if (fclose(dest_fp) == EOF) {
3181 FILE_OP_ERROR(dest, "fclose");
3182 g_unlink(dest);
3183 return -1;
3184 }
3185
3186 return 0;
3187}
3188
3189gint copy_file_stream(FILE *fp, FILE *dest_fp)
3190{
3191 gint n_read;
3192 gchar buf[BUFFSIZE];
3193
3194 g_return_val_if_fail(fp != NULL, -1);
3195 g_return_val_if_fail(dest_fp != NULL, -1);
3196
3197 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3198 if (n_read < sizeof(buf) && ferror(fp))
3199 break;
3200 if (fwrite(buf, n_read, 1, dest_fp) < 1) {
3201 g_warning("copy_file_stream: writing to file failed.\n");
3202 return -1;
3203 }
3204 }
3205
3206 if (ferror(fp)) {
3207 perror("fread");
3208 return -1;
3209 }
3210 if (fflush(dest_fp) == EOF) {
3211 FILE_OP_ERROR("copy_file_stream", "fflush");
3212 return -1;
3213 }
3214
3215 return 0;
3216}
3217
3218/* convert line endings into CRLF. If the last line doesn't end with
3219 * linebreak, add it.
3220 */
3221gchar *canonicalize_str(const gchar *str)
3222{
3223 const gchar *p;
3224 guint new_len = 0;
3225 gchar *out, *outp;
3226
3227 for (p = str; *p != '\0'; ++p) {
3228 if (*p != '\r') {
3229 ++new_len;
3230 if (*p == '\n')
3231 ++new_len;
3232 }
3233 }
3234 if (p == str || *(p - 1) != '\n')
3235 new_len += 2;
3236
3237 out = outp = g_malloc(new_len + 1);
3238 for (p = str; *p != '\0'; ++p) {
3239 if (*p != '\r') {
3240 if (*p == '\n')
3241 *outp++ = '\r';
3242 *outp++ = *p;
3243 }
3244 }
3245 if (p == str || *(p - 1) != '\n') {
3246 *outp++ = '\r';
3247 *outp++ = '\n';
3248 }
3249 *outp = '\0';
3250
3251 return out;
3252}
3253
3254gint canonicalize_file(const gchar *src, const gchar *dest)
3255{
3256 FILE *src_fp, *dest_fp;
3257 gchar buf[BUFFSIZE];
3258 gint len;
3259 gboolean err = FALSE;
3260 gboolean last_linebreak = FALSE;
3261
3262 if ((src_fp = g_fopen(src, "rb")) == NULL) {
3263 FILE_OP_ERROR(src, "fopen");
3264 return -1;
3265 }
3266
3267 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
3268 FILE_OP_ERROR(dest, "fopen");
3269 fclose(src_fp);
3270 return -1;
3271 }
3272
3273 if (change_file_mode_rw(dest_fp, dest) < 0) {
3274 FILE_OP_ERROR(dest, "chmod");
3275 g_warning("can't change file mode\n");
3276 }
3277
3278 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
3279 gint r = 0;
3280
3281 len = strlen(buf);
3282 if (len == 0) break;
3283 last_linebreak = FALSE;
3284
3285 if (buf[len - 1] != '\n') {
3286 last_linebreak = TRUE;
3287 r = fputs(buf, dest_fp);
3288 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
3289 r = fputs(buf, dest_fp);
3290 } else {
3291 if (len > 1) {
3292 r = fwrite(buf, len - 1, 1, dest_fp);
3293 if (r != 1)
3294 r = EOF;
3295 }
3296 if (r != EOF)
3297 r = fputs("\r\n", dest_fp);
3298 }
3299
3300 if (r == EOF) {
3301 g_warning("writing to %s failed.\n", dest);
3302 fclose(dest_fp);
3303 fclose(src_fp);
3304 g_unlink(dest);
3305 return -1;
3306 }
3307 }
3308
3309 if (last_linebreak == TRUE) {
3310 if (fputs("\r\n", dest_fp) == EOF)
3311 err = TRUE;
3312 }
3313
3314 if (ferror(src_fp)) {
3315 FILE_OP_ERROR(src, "fgets");
3316 err = TRUE;
3317 }
3318 fclose(src_fp);
3319 if (fclose(dest_fp) == EOF) {
3320 FILE_OP_ERROR(dest, "fclose");
3321 err = TRUE;
3322 }
3323
3324 if (err) {
3325 g_unlink(dest);
3326 return -1;
3327 }
3328
3329 return 0;
3330}
3331
3332gint canonicalize_file_replace(const gchar *file)
3333{
3334 gchar *tmp_file;
3335
3336 tmp_file = get_tmp_file();
3337
3338 if (canonicalize_file(file, tmp_file) < 0) {
3339 g_free(tmp_file);
3340 return -1;
3341 }
3342
3343 if (move_file(tmp_file, file, TRUE) < 0) {
3344 g_warning("can't replace %s .\n", file);
3345 g_unlink(tmp_file);
3346 g_free(tmp_file);
3347 return -1;
3348 }
3349
3350 g_free(tmp_file);
3351 return 0;
3352}
3353
3354FILE *canonicalize_file_stream(FILE *src_fp, gint *length)
3355{
3356 FILE *dest_fp;
3357 gchar buf[BUFFSIZE];
3358 gint len;
3359 gint length_ = 0;
3360 gboolean err = FALSE;
3361 gboolean last_linebreak = FALSE;
3362
3363 if ((dest_fp = my_tmpfile()) == NULL)
3364 return NULL;
3365
3366 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
3367 gint r = 0;
3368
3369 len = strlen(buf);
3370 if (len == 0) break;
3371 last_linebreak = FALSE;
3372
3373 if (buf[len - 1] != '\n') {
3374 last_linebreak = TRUE;
3375 r = fputs(buf, dest_fp);
3376 length_ += len;
3377 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
3378 r = fputs(buf, dest_fp);
3379 length_ += len;
3380 } else {
3381 if (len > 1) {
3382 r = fwrite(buf, len - 1, 1, dest_fp);
3383 if (r != 1)
3384 r = EOF;
3385 else
3386 length_ += len - 1;
3387 }
3388 if (r != EOF) {
3389 r = fputs("\r\n", dest_fp);
3390 length_ += 2;
3391 }
3392 }
3393
3394 if (r == EOF) {
3395 g_warning("writing to temporary file failed.\n");
3396 fclose(dest_fp);
3397 return NULL;
3398 }
3399 }
3400
3401 if (last_linebreak == TRUE) {
3402 if (fputs("\r\n", dest_fp) == EOF)
3403 err = TRUE;
3404 else
3405 length_ += 2;
3406 }
3407
3408 if (ferror(src_fp)) {
3409 FILE_OP_ERROR("canonicalize_file_stream", "fgets");
3410 err = TRUE;
3411 }
3412 if (fflush(dest_fp) == EOF) {
3413 FILE_OP_ERROR("canonicalize_file_stream", "fflush");
3414 err = TRUE;
3415 }
3416
3417 if (err) {
3418 fclose(dest_fp);
3419 return NULL;
3420 }
3421
3422 if (length)
3423 *length = length_;
3424
3425 rewind(dest_fp);
3426 return dest_fp;
3427}
3428
3429gint uncanonicalize_file(const gchar *src, const gchar *dest)
3430{
3431 FILE *src_fp, *dest_fp;
3432 gchar buf[BUFFSIZE];
3433 gboolean err = FALSE;
3434
3435 if ((src_fp = g_fopen(src, "rb")) == NULL) {
3436 FILE_OP_ERROR(src, "fopen");
3437 return -1;
3438 }
3439
3440 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
3441 FILE_OP_ERROR(dest, "fopen");
3442 fclose(src_fp);
3443 return -1;
3444 }
3445
3446 if (change_file_mode_rw(dest_fp, dest) < 0) {
3447 FILE_OP_ERROR(dest, "chmod");
3448 g_warning("can't change file mode\n");
3449 }
3450
3451 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
3452 strcrchomp(buf);
3453 if (fputs(buf, dest_fp) == EOF) {
3454 g_warning("writing to %s failed.\n", dest);
3455 fclose(dest_fp);
3456 fclose(src_fp);
3457 g_unlink(dest);
3458 return -1;
3459 }
3460 }
3461
3462 if (ferror(src_fp)) {
3463 FILE_OP_ERROR(src, "fgets");
3464 err = TRUE;
3465 }
3466 fclose(src_fp);
3467 if (fclose(dest_fp) == EOF) {
3468 FILE_OP_ERROR(dest, "fclose");
3469 err = TRUE;
3470 }
3471
3472 if (err) {
3473 g_unlink(dest);
3474 return -1;
3475 }
3476
3477 return 0;
3478}
3479
3480gint uncanonicalize_file_replace(const gchar *file)
3481{
3482 gchar *tmp_file;
3483
3484 tmp_file = get_tmp_file();
3485
3486 if (uncanonicalize_file(file, tmp_file) < 0) {
3487 g_free(tmp_file);
3488 return -1;
3489 }
3490
3491 if (move_file(tmp_file, file, TRUE) < 0) {
3492 g_warning("can't replace %s .\n", file);
3493 g_unlink(tmp_file);
3494 g_free(tmp_file);
3495 return -1;
3496 }
3497
3498 g_free(tmp_file);
3499 return 0;
3500}
3501
3502gchar *normalize_newlines(const gchar *str)
3503{
3504 const gchar *p = str;
3505 gchar *out, *outp;
3506
3507 out = outp = g_malloc(strlen(str) + 1);
3508 for (p = str; *p != '\0'; ++p) {
3509 if (*p == '\r') {
3510 if (*(p + 1) != '\n')
3511 *outp++ = '\n';
3512 } else
3513 *outp++ = *p;
3514 }
3515
3516 *outp = '\0';
3517
3518 return out;
3519}
3520
3521gchar *strchomp_all(const gchar *str)
3522{
3523 const gchar *p = str;
3524 const gchar *newline, *last;
3525 gchar *out, *outp;
3526
3527 out = outp = g_malloc(strlen(str) + 1);
3528 while (*p != '\0') {
3529 newline = strchr(p, '\n');
3530 if (newline) {
3531 for (last = newline;
3532 p < last && g_ascii_isspace(*(last - 1)); --last)
3533 ;
3534 strncpy(outp, p, last - p);
3535 outp += last - p;
3536
3537 if (p < newline && *(newline - 1) == '\r') {
3538 strncpy(outp, newline - 1, 2);
3539 outp += 2;
3540 } else {
3541 *outp++ = *newline;
3542 }
3543
3544 p = newline + 1;
3545 } else {
3546 for (last = p + strlen(p);
3547 p < last && g_ascii_isspace(*(last - 1)); --last)
3548 ;
3549 strncpy(outp, p, last - p);
3550 outp += last - p;
3551 break;
3552 }
3553 }
3554
3555 *outp = '\0';
3556
3557 return out;
3558}
3559
3560FILE *get_outgoing_rfc2822_file(FILE *fp)
3561{
3562 gchar buf[BUFFSIZE];
3563 FILE *outfp;
3564
3565 outfp = my_tmpfile();
3566 if (!outfp) {
3567 FILE_OP_ERROR("get_outgoing_rfc2822_file", "my_tmpfile");
3568 return NULL;
3569 }
3570
3571 /* output header part */
3572 while (fgets(buf, sizeof(buf), fp) != NULL) {
3573 strretchomp(buf);
3574 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
3575 gint next;
3576
3577 for (;;) {
3578 next = fgetc(fp);
3579 if (next == EOF)
3580 break;
3581 else if (next != ' ' && next != '\t') {
3582 ungetc(next, fp);
3583 break;
3584 }
3585 if (fgets(buf, sizeof(buf), fp) == NULL)
3586 break;
3587 }
3588 } else {
3589 if (fputs(buf, outfp) == EOF)
3590 goto file_error;
3591 if (fputs("\r\n", outfp) == EOF)
3592 goto file_error;
3593 if (buf[0] == '\0')
3594 break;
3595 }
3596 }
3597
3598 /* output body part */
3599 while (fgets(buf, sizeof(buf), fp) != NULL) {
3600 strretchomp(buf);
3601 if (buf[0] == '.') {
3602 if (fputc('.', outfp) == EOF)
3603 goto file_error;
3604 }
3605 if (fputs(buf, outfp) == EOF)
3606 goto file_error;
3607 if (fputs("\r\n", outfp) == EOF)
3608 goto file_error;
3609 }
3610
3611 if (fflush(outfp) == EOF) {
3612 FILE_OP_ERROR("get_outgoing_rfc2822_file", "fflush");
3613 goto file_error;
3614 }
3615
3616 rewind(outfp);
3617 return outfp;
3618
3619file_error:
3620 g_warning("get_outgoing_rfc2822_file(): writing to temporary file failed.\n");
3621 fclose(outfp);
3622 return NULL;
3623}
3624
3625gchar *get_outgoing_rfc2822_str(FILE *fp)
3626{
3627 gchar buf[BUFFSIZE];
3628 GString *str;
3629 gchar *ret;
3630
3631 str = g_string_new(NULL);
3632
3633 /* output header part */
3634 while (fgets(buf, sizeof(buf), fp) != NULL) {
3635 strretchomp(buf);
3636 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
3637 gint next;
3638
3639 for (;;) {
3640 next = fgetc(fp);
3641 if (next == EOF)
3642 break;
3643 else if (next != ' ' && next != '\t') {
3644 ungetc(next, fp);
3645 break;
3646 }
3647 if (fgets(buf, sizeof(buf), fp) == NULL)
3648 break;
3649 }
3650#if 0
3651 } else if (!g_ascii_strncasecmp(buf, "Date:", 5)) {
3652 get_rfc822_date(buf, sizeof(buf));
3653 g_string_append_printf(str, "Date: %s\r\n", buf);
3654#endif
3655 } else {
3656 g_string_append(str, buf);
3657 g_string_append(str, "\r\n");
3658 if (buf[0] == '\0')
3659 break;
3660 }
3661 }
3662
3663 /* output body part */
3664 while (fgets(buf, sizeof(buf), fp) != NULL) {
3665 strretchomp(buf);
3666 if (buf[0] == '.')
3667 g_string_append_c(str, '.');
3668 g_string_append(str, buf);
3669 g_string_append(str, "\r\n");
3670 }
3671
3672 ret = str->str;
3673 g_string_free(str, FALSE);
3674
3675 return ret;
3676}
3677
3678/*
3679 * Create a new boundary in a way that it is very unlikely that this
3680 * will occur in the following text. It would be easy to ensure
3681 * uniqueness if everything is either quoted-printable or base64
3682 * encoded (note that conversion is allowed), but because MIME bodies
3683 * may be nested, it may happen that the same boundary has already
3684 * been used. We avoid scanning the message for conflicts and hope the
3685 * best.
3686 *
3687 * boundary := 0*69<bchars> bcharsnospace
3688 * bchars := bcharsnospace / " "
3689 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3690 * "+" / "_" / "," / "-" / "." /
3691 * "/" / ":" / "=" / "?"
3692 *
3693 * some special characters removed because of buggy MTAs
3694 */
3695
3696gchar *generate_mime_boundary(const gchar *prefix)
3697{
3698 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3699 "abcdefghijklmnopqrstuvwxyz"
3700 "1234567890+_./=";
3701 gchar buf_uniq[17];
3702 gchar buf_date[64];
3703 gint i;
3704
3705 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3706 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3707 buf_uniq[i] = '\0';
3708
3709 get_rfc822_date(buf_date, sizeof(buf_date));
3710 subst_chars(buf_date, " ,:", '_');
3711
3712 return g_strdup_printf("%s=_%s_%s", prefix ? prefix : "Multipart",
3713 buf_date, buf_uniq);
3714}
3715
3716gint change_file_mode_rw(FILE *fp, const gchar *file)
3717{
3718#ifdef G_OS_WIN32
3719 DWORD attr;
3720 BOOL retval;
3721
3722 if (G_WIN32_HAVE_WIDECHAR_API()) {
3723 wchar_t *wpath;
3724
3725 wpath = g_utf8_to_utf16(file, -1, NULL, NULL, NULL);
3726 if (wpath == NULL)
3727 return -1;
3728
3729 attr = GetFileAttributesW(wpath);
3730 retval = SetFileAttributesW
3731 (wpath, attr & ~(FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN));
3732
3733 g_free(wpath);
3734 } else {
3735 gchar *cp_path;
3736
3737 cp_path = g_locale_from_utf8(file, -1, NULL, NULL, NULL);
3738 if (cp_path == NULL)
3739 return -1;
3740
3741 attr = GetFileAttributesA(cp_path);
3742 retval = SetFileAttributesA
3743 (cp_path, attr & ~(FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN));
3744
3745 g_free(cp_path);
3746 }
3747
3748 if (retval)
3749 return 0;
3750 else
3751 return -1;
3752#else
3753#if HAVE_FCHMOD
3754 if (fp)
3755 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3756 else
3757#endif
3758 return g_chmod(file, S_IRUSR|S_IWUSR);
3759#endif
3760}
3761
3762#ifdef G_OS_WIN32
3763gchar *_s_tempnam(const gchar *dir, const gchar *prefix)
3764{
3765 if (G_WIN32_HAVE_WIDECHAR_API()) {
3766 wchar_t *wpath;
3767 wchar_t *wprefix;
3768 wchar_t *wname;
3769 gint save_errno;
3770 gchar *name;
3771
3772 wpath = g_utf8_to_utf16(dir, -1, NULL, NULL, NULL);
3773 if (wpath == NULL) {
3774 errno = EINVAL;
3775 return NULL;
3776 }
3777 wprefix = g_utf8_to_utf16(prefix, -1, NULL, NULL, NULL);
3778 if (wprefix == NULL) {
3779 errno = EINVAL;
3780 g_free(wpath);
3781 return NULL;
3782 }
3783
3784 wname = _wtempnam(wpath, wprefix);
3785 save_errno = errno;
3786
3787 name = g_utf16_to_utf8(wname, -1, NULL, NULL, NULL);
3788 if (name == NULL) {
3789 save_errno = EINVAL;
3790 }
3791
3792 free(wname); /* must be freed by same MSVCRT used for
3793 _wtempnam() */
3794 g_free(wprefix);
3795 g_free(wpath);
3796
3797 errno = save_errno;
3798 return name;
3799 } else {
3800 gchar *cp_path;
3801 gchar *cp_prefix;
3802 gchar *cp_name;
3803 gint save_errno;
3804 gchar *name;
3805
3806 cp_path = g_locale_from_utf8(dir, -1, NULL, NULL, NULL);
3807 if (cp_path == NULL) {
3808 errno = EINVAL;
3809 return NULL;
3810 }
3811
3812 cp_prefix = g_locale_from_utf8(prefix, -1, NULL, NULL, NULL);
3813 if (cp_prefix == NULL) {
3814 errno = EINVAL;
3815 g_free(cp_path);
3816 return NULL;
3817 }
3818
3819 cp_name = _tempnam(cp_path, cp_prefix);
3820 save_errno = errno;
3821
3822 name = g_locale_to_utf8(cp_name, -1, NULL, NULL, NULL);
3823 if (name == NULL) {
3824 save_errno = EINVAL;
3825 }
3826
3827 free(cp_name);
3828 g_free(cp_prefix);
3829 g_free(cp_path);
3830
3831 errno = save_errno;
3832 return name;
3833 }
3834}
3835#endif
3836
3837FILE *my_tmpfile(void)
3838{
3839#ifdef G_OS_WIN32
3840 const gchar *tmpdir;
3841 gchar *fname;
3842 gint fd;
3843 FILE *fp;
3844
3845 tmpdir = get_tmp_dir();
3846 fname = _s_tempnam(tmpdir, "sylph");
3847 if (!fname)
3848 return NULL;
3849
3850 fd = g_open(fname, O_RDWR | O_CREAT | O_EXCL |
3851 _O_TEMPORARY | _O_SHORT_LIVED | _O_BINARY, 0600);
3852 if (fd < 0) {
3853 g_free(fname);
3854 return NULL;
3855 }
3856
3857 fp = fdopen(fd, "w+b");
3858 if (!fp) {
3859 perror("fdopen");
3860 close(fd);
3861 }
3862
3863 g_free(fname);
3864
3865 return fp;
3866#else
3867 const gchar suffix[] = ".XXXXXX";
3868 const gchar *tmpdir;
3869 guint tmplen;
3870 const gchar *progname;
3871 guint proglen;
3872 gchar *fname;
3873 gint fd;
3874 FILE *fp;
3875
3876 tmpdir = get_tmp_dir();
3877 tmplen = strlen(tmpdir);
3878 progname = g_get_prgname();
3879 if (!progname)
3880 progname = "sylph";
3881 proglen = strlen(progname);
3882 fname = g_malloc(tmplen + 1 + proglen + sizeof(suffix));
3883
3884 memcpy(fname, tmpdir, tmplen);
3885 fname[tmplen] = G_DIR_SEPARATOR;
3886 memcpy(fname + tmplen + 1, progname, proglen);
3887 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3888
3889 fd = g_mkstemp(fname);
3890 if (fd < 0) {
3891 g_free(fname);
3892 return tmpfile();
3893 }
3894
3895 g_unlink(fname);
3896
3897 fp = fdopen(fd, "w+b");
3898 if (!fp) {
3899 perror("fdopen");
3900 close(fd);
3901 }
3902
3903 g_free(fname);
3904
3905 return fp;
3906#endif
3907}
3908
3909FILE *str_open_as_stream(const gchar *str)
3910{
3911 FILE *fp;
3912 size_t len;
3913
3914 g_return_val_if_fail(str != NULL, NULL);
3915
3916 fp = my_tmpfile();
3917 if (!fp) {
3918 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3919 return NULL;
3920 }
3921
3922 len = strlen(str);
3923 if (len == 0) return fp;
3924
3925 if (fwrite(str, len, 1, fp) != 1) {
3926 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3927 fclose(fp);
3928 return NULL;
3929 }
3930 if (fflush(fp) == EOF) {
3931 FILE_OP_ERROR("str_open_as_stream", "fflush");
3932 fclose(fp);
3933 return NULL;
3934 }
3935
3936 rewind(fp);
3937 return fp;
3938}
3939
3940gint str_write_to_file(const gchar *str, const gchar *file)
3941{
3942 FILE *fp;
3943 size_t len;
3944
3945 g_return_val_if_fail(str != NULL, -1);
3946 g_return_val_if_fail(file != NULL, -1);
3947
3948 if ((fp = g_fopen(file, "wb")) == NULL) {
3949 FILE_OP_ERROR(file, "fopen");
3950 return -1;
3951 }
3952
3953 len = strlen(str);
3954 if (len == 0) {
3955 fclose(fp);
3956 return 0;
3957 }
3958
3959 if (fwrite(str, len, 1, fp) != 1) {
3960 FILE_OP_ERROR(file, "fwrite");
3961 fclose(fp);
3962 g_unlink(file);
3963 return -1;
3964 }
3965
3966 if (fclose(fp) == EOF) {
3967 FILE_OP_ERROR(file, "fclose");
3968 g_unlink(file);
3969 return -1;
3970 }
3971
3972 return 0;
3973}
3974
3975gchar *file_read_to_str(const gchar *file)
3976{
3977 FILE *fp;
3978 gchar *str;
3979
3980 g_return_val_if_fail(file != NULL, NULL);
3981
3982 if ((fp = g_fopen(file, "rb")) == NULL) {
3983 FILE_OP_ERROR(file, "fopen");
3984 return NULL;
3985 }
3986
3987 str = file_read_stream_to_str(fp);
3988
3989 fclose(fp);
3990
3991 return str;
3992}
3993
3994gchar *file_read_stream_to_str(FILE *fp)
3995{
3996 GByteArray *array;
3997 guchar buf[BUFSIZ];
3998 gint n_read;
3999 gchar *str;
4000
4001 g_return_val_if_fail(fp != NULL, NULL);
4002
4003 array = g_byte_array_new();
4004
4005 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4006 if (n_read < sizeof(buf) && ferror(fp))
4007 break;
4008 g_byte_array_append(array, buf, n_read);
4009 }
4010
4011 if (ferror(fp)) {
4012 FILE_OP_ERROR("file stream", "fread");
4013 g_byte_array_free(array, TRUE);
4014 return NULL;
4015 }
4016
4017 buf[0] = '\0';
4018 g_byte_array_append(array, buf, 1);
4019 str = (gchar *)array->data;
4020 g_byte_array_free(array, FALSE);
4021
4022 return str;
4023}
4024
4025#if defined(G_OS_WIN32) && !GLIB_CHECK_VERSION(2, 8, 2)
4026static gchar **argv_utf8_to_locale(gchar **argv)
4027{
4028 gint argc = 0, i;
4029 gchar **cp_argv;
4030
4031 while (argv[argc] != NULL)
4032 argc++;
4033
4034 cp_argv = g_new(gchar *, argc + 1);
4035
4036 for (i = 0; i < argc; i++) {
4037 cp_argv[i] = g_locale_from_utf8(argv[i], -1, NULL, NULL, NULL);
4038 if (cp_argv[i] == NULL) {
4039 g_warning("Failed to convert from UTF-8 to locale encoding: %s\n", argv[i]);
4040 g_strfreev(cp_argv);
4041 return NULL;
4042 }
4043 }
4044 cp_argv[i] = NULL;
4045
4046 return cp_argv;
4047}
4048#endif
4049
4050gint execute_async(gchar *const argv[])
4051{
4052#if defined(G_OS_WIN32) && !GLIB_CHECK_VERSION(2, 8, 2)
4053 gchar **cp_argv;
4054
4055 g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
4056
4057 cp_argv = argv_utf8_to_locale((gchar **)argv);
4058 if (!cp_argv)
4059 return -1;
4060 if (g_spawn_async(NULL, cp_argv, NULL, G_SPAWN_SEARCH_PATH,
4061 NULL, NULL, NULL, NULL) == FALSE) {
4062 g_warning("Can't execute command: %s\n", argv[0]);
4063 g_strfreev(cp_argv);
4064 return -1;
4065 }
4066 g_strfreev(cp_argv);
4067#else
4068 g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
4069
4070 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
4071 NULL, NULL, NULL, NULL) == FALSE) {
4072 g_warning("Can't execute command: %s\n", argv[0]);
4073 return -1;
4074 }
4075#endif
4076
4077 return 0;
4078}
4079
4080gint execute_sync(gchar *const argv[])
4081{
4082 gint status;
4083#if defined(G_OS_WIN32) && !GLIB_CHECK_VERSION(2, 8, 2)
4084 gchar **cp_argv;
4085#endif
4086
4087 g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
4088
4089#ifdef G_OS_WIN32
4090#if !GLIB_CHECK_VERSION(2, 8, 2)
4091 cp_argv = argv_utf8_to_locale((gchar **)argv);
4092 if (!cp_argv)
4093 return -1;
4094 if (g_spawn_sync(NULL, cp_argv, NULL,
4095 G_SPAWN_SEARCH_PATH | G_SPAWN_CHILD_INHERITS_STDIN |
4096 G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
4097 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
4098 g_warning("Can't execute command: %s\n", argv[0]);
4099 g_strfreev(cp_argv);
4100 return -1;
4101 }
4102 g_strfreev(cp_argv);
4103#else /* !GLIB_CHECK_VERSION */
4104 if (g_spawn_sync(NULL, (gchar **)argv, NULL,
4105 G_SPAWN_SEARCH_PATH | G_SPAWN_CHILD_INHERITS_STDIN |
4106 G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
4107 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
4108 g_warning("Can't execute command: %s\n", argv[0]);
4109 return -1;
4110 }
4111#endif /* !GLIB_CHECK_VERSION */
4112
4113 return status;
4114#else /* G_OS_WIN32 */
4115 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
4116 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
4117 g_warning("Can't execute command: %s\n", argv[0]);
4118 return -1;
4119 }
4120
4121 if (WIFEXITED(status))
4122 return WEXITSTATUS(status);
4123 else
4124 return -1;
4125#endif /* G_OS_WIN32 */
4126}
4127
4128gint execute_command_line(const gchar *cmdline, gboolean async)
4129{
4130 gchar **argv;
4131 gint ret;
4132
4133 if (debug_mode) {
4134 gchar *utf8_cmdline;
4135
4136 utf8_cmdline = g_filename_to_utf8
4137 (cmdline, -1, NULL, NULL, NULL);
4138 debug_print("execute_command_line(): executing: %s\n",
4139 utf8_cmdline ? utf8_cmdline : cmdline);
4140 g_free(utf8_cmdline);
4141 }
4142
4143 argv = strsplit_with_quote(cmdline, " ", 0);
4144
4145 if (async)
4146 ret = execute_async(argv);
4147 else
4148 ret = execute_sync(argv);
4149
4150 g_strfreev(argv);
4151
4152 return ret;
4153}
4154
4155#if USE_THREADS
4156typedef struct _CmdData
4157{
4158 const gchar *cmdline;
4159 volatile gint flag;
4160 gint status;
4161} CmdData;
4162
4163static gpointer execute_command_line_async_func(gpointer data)
4164{
4165 CmdData *cmd_data = (CmdData *)data;
4166 gchar **argv;
4167
4168 argv = strsplit_with_quote(cmd_data->cmdline, " ", 0);
4169 cmd_data->status = execute_sync(argv);
4170 g_strfreev(argv);
4171
4172 debug_print("execute_command_line_async_func: exec done: %s\n",
4173 cmd_data->cmdline);
4174 g_atomic_int_set(&cmd_data->flag, 1);
4175 g_main_context_wakeup(NULL);
4176
4177 return GINT_TO_POINTER(0);
4178}
4179
4180gint execute_command_line_async_wait(const gchar *cmdline)
4181{
4182 CmdData data = {NULL, 0, 0};
4183 GThread *thread;
4184
4185 if (debug_mode) {
4186 gchar *utf8_cmdline;
4187
4188 utf8_cmdline = g_filename_to_utf8
4189 (cmdline, -1, NULL, NULL, NULL);
4190 debug_print("execute_command_line(): executing: %s\n",
4191 utf8_cmdline ? utf8_cmdline : cmdline);
4192 g_free(utf8_cmdline);
4193 }
4194
4195 data.cmdline = cmdline;
4196 thread = g_thread_create(execute_command_line_async_func, &data, TRUE,
4197 NULL);
4198 if (!thread)
4199 return -1;
4200
4201 debug_print("execute_command_line_async_wait: waiting thread\n");
4202 while (g_atomic_int_get(&data.flag) == 0)
4203 event_loop_iterate();
4204
4205 g_thread_join(thread);
4206 debug_print("execute_command_line_async_wait: thread exited\n");
4207
4208 return data.status;
4209}
4210#else /* USE_THREADS */
4211gint execute_command_line_async_wait(const gchar *cmdline)
4212{
4213 return execute_command_line(cmdline, FALSE);
4214}
4215#endif /* USE_THREADS */
4216
4217gint execute_open_file(const gchar *file, const gchar *content_type)
4218{
4219#ifdef G_OS_WIN32
4220 g_return_val_if_fail(file != NULL, -1);
4221
4222 log_print("opening %s - %s\n", file, content_type ? content_type : "");
4223
4224 if (G_WIN32_HAVE_WIDECHAR_API()) {
4225 wchar_t *wpath;
4226
4227 wpath = g_utf8_to_utf16(file, -1, NULL, NULL, NULL);
4228 if (wpath == NULL)
4229 return -1;
4230
4231 ShellExecuteW(NULL, L"open", wpath, NULL, NULL, SW_SHOWNORMAL);
4232
4233 g_free(wpath);
4234
4235 return 0;
4236 } else {
4237 gchar *cp_path;
4238
4239 cp_path = g_locale_from_utf8(file, -1, NULL, NULL, NULL);
4240 if (cp_path == NULL)
4241 return -1;
4242
4243 ShellExecuteA(NULL, "open", cp_path, NULL, NULL, SW_SHOWNORMAL);
4244
4245 g_free(cp_path);
4246
4247 return 0;
4248 }
4249#elif defined(__APPLE__)
4250 const gchar *argv[3] = {"open", NULL, NULL};
4251
4252 g_return_val_if_fail(file != NULL, -1);
4253
4254 log_print("opening %s - %s\n", file, content_type ? content_type : "");
4255
4256 argv[1] = file;
4257 execute_async(argv);
4258#else
4259 const gchar *argv[3] = {"xdg-open", NULL, NULL};
4260
4261 g_return_val_if_fail(file != NULL, -1);
4262
4263 log_print("opening %s - %s\n", file, content_type ? content_type : "");
4264
4265 argv[1] = file;
4266 execute_async(argv);
4267#endif
4268
4269 return 0;
4270}
4271
4272gint execute_print_file(const gchar *file)
4273{
4274 g_return_val_if_fail(file != NULL, -1);
4275
4276#ifdef G_OS_WIN32
4277 log_print("printing %s\n", file);
4278
4279 if (G_WIN32_HAVE_WIDECHAR_API()) {
4280 wchar_t *wpath;
4281
4282 wpath = g_utf8_to_utf16(file, -1, NULL, NULL, NULL);
4283 if (wpath == NULL)
4284 return -1;
4285
4286 ShellExecuteW(NULL, L"print", wpath, NULL, NULL, SW_SHOWNORMAL);
4287
4288 g_free(wpath);
4289
4290 return 0;
4291 } else {
4292 gchar *cp_path;
4293
4294 cp_path = g_locale_from_utf8(file, -1, NULL, NULL, NULL);
4295 if (cp_path == NULL)
4296 return -1;
4297
4298 ShellExecuteA(NULL, "print", cp_path, NULL, NULL,
4299 SW_SHOWNORMAL);
4300
4301 g_free(cp_path);
4302
4303 return 0;
4304 }
4305#endif
4306 return 0;
4307}
4308
4309gchar *get_command_output(const gchar *cmdline)
4310{
4311 gchar *child_stdout;
4312 gint status;
4313
4314 g_return_val_if_fail(cmdline != NULL, NULL);
4315
4316 debug_print("get_command_output(): executing: %s\n", cmdline);
4317
4318 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
4319 NULL) == FALSE) {
4320 g_warning("Can't execute command: %s\n", cmdline);
4321 return NULL;
4322 }
4323
4324 return child_stdout;
4325}
4326
4327gint open_uri(const gchar *uri, const gchar *cmdline)
4328{
4329 gchar buf[BUFFSIZE];
4330
4331 g_return_val_if_fail(uri != NULL, -1);
4332
4333#if defined(G_OS_WIN32) || defined(__APPLE__)
4334 if (!cmdline || cmdline[0] == '\0')
4335 return execute_open_file(uri, NULL);
4336#endif
4337
4338 if (cmdline && str_find_format_times(cmdline, 's') == 1)
4339 g_snprintf(buf, sizeof(buf), cmdline, uri);
4340 else {
4341 if (cmdline)
4342 g_warning("Open URI command line is invalid "
4343 "(there must be only one '%%s'): %s",
4344 cmdline);
4345 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, uri);
4346 }
4347
4348 execute_command_line(buf, TRUE);
4349
4350 return 0;
4351}
4352
4353gint play_sound(const gchar *file, gboolean async)
4354{
4355#ifdef G_OS_WIN32
4356 wchar_t *wfile;
4357 DWORD flag = SND_FILENAME;
4358
4359 wfile = g_utf8_to_utf16(file, -1, NULL, NULL, NULL);
4360 if (wfile == NULL)
4361 return -1;
4362 if (async)
4363 flag |= SND_ASYNC;
4364 else
4365 flag |= SND_SYNC;
4366 PlaySoundW(wfile, NULL, flag);
4367 g_free(wfile);
4368#endif
4369 return 0;
4370}
4371
4372stime_t remote_tzoffset_sec(const gchar *zone)
4373{
4374 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
4375 gchar zone3[4];
4376 gchar *p;
4377 gchar c;
4378 gint iustz;
4379 gint offset;
4380 time_t remoteoffset;
4381
4382 strncpy(zone3, zone, 3);
4383 zone3[3] = '\0';
4384 remoteoffset = 0;
4385
4386 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
4387 (c == '+' || c == '-')) {
4388 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
4389 if (c == '-')
4390 remoteoffset = -remoteoffset;
4391 } else if (!strncmp(zone, "UT" , 2) ||
4392 !strncmp(zone, "GMT", 2)) {
4393 remoteoffset = 0;
4394 } else if (strlen(zone3) == 3) {
4395 for (p = ustzstr; *p != '\0'; p += 3) {
4396 if (!g_ascii_strncasecmp(p, zone3, 3)) {
4397 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
4398 remoteoffset = iustz * 3600;
4399 break;
4400 }
4401 }
4402 if (*p == '\0')
4403 return -1;
4404 } else if (strlen(zone3) == 1) {
4405 switch (zone[0]) {
4406 case 'Z': remoteoffset = 0; break;
4407 case 'A': remoteoffset = -1; break;
4408 case 'B': remoteoffset = -2; break;
4409 case 'C': remoteoffset = -3; break;
4410 case 'D': remoteoffset = -4; break;
4411 case 'E': remoteoffset = -5; break;
4412 case 'F': remoteoffset = -6; break;
4413 case 'G': remoteoffset = -7; break;
4414 case 'H': remoteoffset = -8; break;
4415 case 'I': remoteoffset = -9; break;
4416 case 'K': remoteoffset = -10; break; /* J is not used */
4417 case 'L': remoteoffset = -11; break;
4418 case 'M': remoteoffset = -12; break;
4419 case 'N': remoteoffset = 1; break;
4420 case 'O': remoteoffset = 2; break;
4421 case 'P': remoteoffset = 3; break;
4422 case 'Q': remoteoffset = 4; break;
4423 case 'R': remoteoffset = 5; break;
4424 case 'S': remoteoffset = 6; break;
4425 case 'T': remoteoffset = 7; break;
4426 case 'U': remoteoffset = 8; break;
4427 case 'V': remoteoffset = 9; break;
4428 case 'W': remoteoffset = 10; break;
4429 case 'X': remoteoffset = 11; break;
4430 case 'Y': remoteoffset = 12; break;
4431 default: remoteoffset = 0; break;
4432 }
4433 remoteoffset = remoteoffset * 3600;
4434 } else
4435 return -1;
4436
4437 return remoteoffset;
4438}
4439
4440stime_t tzoffset_sec(stime_t *now)
4441{
4442 time_t now_ = *now;
4443 struct tm gmt, *tmp, *lt;
4444 gint off;
4445
4446 tmp = gmtime(&now_);
4447 g_return_val_if_fail(tmp != NULL, -1);
4448 gmt = *tmp;
4449 lt = localtime(&now_);
4450 g_return_val_if_fail(lt != NULL, -1);
4451
4452 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
4453
4454 if (lt->tm_year < gmt.tm_year)
4455 off -= 24 * 60;
4456 else if (lt->tm_year > gmt.tm_year)
4457 off += 24 * 60;
4458 else if (lt->tm_yday < gmt.tm_yday)
4459 off -= 24 * 60;
4460 else if (lt->tm_yday > gmt.tm_yday)
4461 off += 24 * 60;
4462
4463 if (off >= 24 * 60) /* should be impossible */
4464 off = 23 * 60 + 59; /* if not, insert silly value */
4465 if (off <= -24 * 60)
4466 off = -(23 * 60 + 59);
4467
4468 return off * 60;
4469}
4470
4471/* calculate timezone offset (buf must not be less than 6 bytes) */
4472gchar *tzoffset_buf(gchar *buf, stime_t *now)
4473{
4474 time_t now_ = *now;
4475 struct tm gmt, *tmp, *lt;
4476 gint off;
4477 gchar sign = '+';
4478
4479 tmp = gmtime(&now_);
4480 g_return_val_if_fail(tmp != NULL, NULL);
4481 gmt = *tmp;
4482 lt = localtime(&now_);
4483 g_return_val_if_fail(lt != NULL, NULL);
4484
4485 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
4486
4487 if (lt->tm_year < gmt.tm_year)
4488 off -= 24 * 60;
4489 else if (lt->tm_year > gmt.tm_year)
4490 off += 24 * 60;
4491 else if (lt->tm_yday < gmt.tm_yday)
4492 off -= 24 * 60;
4493 else if (lt->tm_yday > gmt.tm_yday)
4494 off += 24 * 60;
4495
4496 if (off < 0) {
4497 sign = '-';
4498 off = -off;
4499 }
4500
4501 if (off >= 24 * 60) /* should be impossible */
4502 off = 23 * 60 + 59; /* if not, insert silly value */
4503
4504 g_snprintf(buf, 6, "%c%02d%02d", sign, off / 60, off % 60);
4505
4506 return buf;
4507}
4508
4509gchar *tzoffset(stime_t *now)
4510{
4511 static gchar offset_string[6];
4512
4513 return tzoffset_buf(offset_string, now);
4514}
4515
4516void get_rfc822_date(gchar *buf, gint len)
4517{
4518 struct tm *lt;
4519 time_t t;
4520 stime_t t_;
4521 gchar day[4], mon[4];
4522 gint dd, hh, mm, ss, yyyy;
4523 gchar off[6];
4524
4525 t_ = t = time(NULL);
4526 lt = localtime(&t);
4527
4528 sscanf(asctime(lt), "%3s %3s %d %d:%d:%d %d\n",
4529 day, mon, &dd, &hh, &mm, &ss, &yyyy);
4530 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
4531 day, dd, mon, yyyy, hh, mm, ss, tzoffset_buf(off, &t_));
4532}
4533
4534/* just a wrapper to suppress the warning of gcc about %c */
4535size_t my_strftime(gchar *s, size_t max, const gchar *format,
4536 const struct tm *tm)
4537{
4538 return strftime(s, max, format, tm);
4539}
4540
4541/* UI hints */
4542
4543static UIUpdateFunc ui_update_func = NULL;
4544
4545void set_ui_update_func(UIUpdateFunc func)
4546{
4547 ui_update_func = func;
4548}
4549
4550void ui_update(void)
4551{
4552 if (ui_update_func)
4553 ui_update_func();
4554}
4555
4556static EventLoopFunc event_loop_func = NULL;
4557
4558void set_event_loop_func(EventLoopFunc func)
4559{
4560 event_loop_func = func;
4561}
4562
4563void event_loop_iterate(void)
4564{
4565 if (event_loop_func)
4566 event_loop_func();
4567 else
4568 g_main_context_iteration(NULL, TRUE);
4569}
4570
4571static ProgressFunc progress_func = NULL;
4572
4573void set_progress_func(ProgressFunc func)
4574{
4575 progress_func = func;
4576}
4577
4578void progress_show(gint cur, gint total)
4579{
4580 if (progress_func)
4581 progress_func(cur, total);
4582}
4583
4584/* user input */
4585
4586static QueryPasswordFunc query_password_func = NULL;
4587
4588void set_input_query_password_func(QueryPasswordFunc func)
4589{
4590 query_password_func = func;
4591}
4592
4593gchar *input_query_password(const gchar *server, const gchar *user)
4594{
4595 if (query_password_func)
4596 return query_password_func(server, user);
4597 else
4598 return NULL;
4599}
4600
4601/* logging */
4602
4603static FILE *log_fp = NULL;
4604#if USE_THREADS
4605G_LOCK_DEFINE_STATIC(log_fp);
4606#define S_LOCK(name) G_LOCK(name)
4607#define S_UNLOCK(name) G_UNLOCK(name)
4608#else
4609#define S_LOCK(name)
4610#define S_UNLOCK(name)
4611#endif
4612
4613void set_log_file(const gchar *filename)
4614{
4615 S_LOCK(log_fp);
4616 if (!log_fp) {
4617 log_fp = g_fopen(filename, "w");
4618 if (!log_fp)
4619 FILE_OP_ERROR(filename, "fopen");
4620 }
4621 S_UNLOCK(log_fp);
4622}
4623
4624void close_log_file(void)
4625{
4626 S_LOCK(log_fp);
4627 if (log_fp) {
4628 fclose(log_fp);
4629 log_fp = NULL;
4630 }
4631 S_UNLOCK(log_fp);
4632}
4633
4634static guint log_verbosity_count = 0;
4635
4636void set_log_verbosity(gboolean verbose)
4637{
4638 if (verbose)
4639 log_verbosity_count++;
4640 else if (log_verbosity_count > 0)
4641 log_verbosity_count--;
4642}
4643
4644gboolean get_debug_mode(void)
4645{
4646 return debug_mode;
4647}
4648
4649void set_debug_mode(gboolean enable)
4650{
4651 debug_mode = enable;
4652}
4653
4654static void log_dummy_func(const gchar *str)
4655{
4656}
4657
4658static void log_dummy_flush_func(void)
4659{
4660}
4661
4662static LogFunc log_print_ui_func = log_dummy_func;
4663static LogFunc log_message_ui_func = log_dummy_func;
4664static LogFunc log_warning_ui_func = log_dummy_func;
4665static LogFunc log_error_ui_func = log_dummy_func;
4666static LogFlushFunc log_flush_ui_func = log_dummy_flush_func;
4667
4668static LogFunc log_show_status_func = log_dummy_func;
4669
4670void set_log_ui_func(LogFunc print_func, LogFunc message_func,
4671 LogFunc warning_func, LogFunc error_func)
4672{
4673 log_print_ui_func = print_func;
4674 log_message_ui_func = message_func;
4675 log_warning_ui_func = warning_func;
4676 log_error_ui_func = error_func;
4677}
4678
4679void set_log_ui_func_full(LogFunc print_func, LogFunc message_func,
4680 LogFunc warning_func, LogFunc error_func,
4681 LogFlushFunc flush_func)
4682{
4683 set_log_ui_func(print_func, message_func, warning_func, error_func);
4684 log_flush_ui_func = flush_func;
4685}
4686
4687void set_log_show_status_func(LogFunc status_func)
4688{
4689 log_show_status_func = status_func;
4690}
4691
4692void debug_print(const gchar *format, ...)
4693{
4694 va_list args;
4695 gchar buf[BUFFSIZE];
4696
4697 if (!debug_mode) return;
4698
4699 va_start(args, format);
4700 g_vsnprintf(buf, sizeof(buf), format, args);
4701 va_end(args);
4702
4703 g_print("%s", buf);
4704}
4705
4706void status_print(const gchar *format, ...)
4707{
4708 va_list args;
4709 gchar buf[BUFFSIZE];
4710
4711 va_start(args, format);
4712 g_vsnprintf(buf, sizeof(buf), format, args);
4713 va_end(args);
4714
4715 log_show_status_func(buf);
4716}
4717
4718#define TIME_LEN 11
4719
4720void log_write(const gchar *str, const gchar *prefix)
4721{
4722 S_LOCK(log_fp);
4723
4724 if (log_fp) {
4725 gchar buf[TIME_LEN + 1];
4726 time_t t;
4727
4728 time(&t);
4729 strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t));
4730
4731 fputs(buf, log_fp);
4732 if (prefix)
4733 fputs(prefix, log_fp);
4734 fputs(str, log_fp);
4735 fflush(log_fp);
4736 }
4737
4738 S_UNLOCK(log_fp);
4739}
4740
4741void log_print(const gchar *format, ...)
4742{
4743 va_list args;
4744 gchar buf[BUFFSIZE + TIME_LEN];
4745 time_t t;
4746
4747 time(&t);
4748 strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t));
4749
4750 va_start(args, format);
4751 g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args);
4752 va_end(args);
4753
4754 if (debug_mode) g_print("%s", buf);
4755 log_print_ui_func(buf);
4756 S_LOCK(log_fp);
4757 if (log_fp) {
4758 fputs(buf, log_fp);
4759 fflush(log_fp);
4760 }
4761 S_UNLOCK(log_fp);
4762 if (log_verbosity_count)
4763 log_show_status_func(buf + TIME_LEN);
4764}
4765
4766void log_message(const gchar *format, ...)
4767{
4768 va_list args;
4769 gchar buf[BUFFSIZE + TIME_LEN];
4770 time_t t;
4771
4772 time(&t);
4773 strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t));
4774
4775 va_start(args, format);
4776 g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args);
4777 va_end(args);
4778
4779 if (debug_mode) g_message("%s", buf + TIME_LEN);
4780 log_message_ui_func(buf + TIME_LEN);
4781 S_LOCK(log_fp);
4782 if (log_fp) {
4783 fwrite(buf, TIME_LEN, 1, log_fp);
4784 fputs("* message: ", log_fp);
4785 fputs(buf + TIME_LEN, log_fp);
4786 fflush(log_fp);
4787 }
4788 S_UNLOCK(log_fp);
4789 log_show_status_func(buf + TIME_LEN);
4790}
4791
4792void log_warning(const gchar *format, ...)
4793{
4794 va_list args;
4795 gchar buf[BUFFSIZE + TIME_LEN];
4796 time_t t;
4797
4798 time(&t);
4799 strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t));
4800
4801 va_start(args, format);
4802 g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args);
4803 va_end(args);
4804
4805 g_warning("%s", buf);
4806 log_warning_ui_func(buf + TIME_LEN);
4807 S_LOCK(log_fp);
4808 if (log_fp) {
4809 fwrite(buf, TIME_LEN, 1, log_fp);
4810 fputs("** warning: ", log_fp);
4811 fputs(buf + TIME_LEN, log_fp);
4812 fflush(log_fp);
4813 }
4814 S_UNLOCK(log_fp);
4815}
4816
4817void log_error(const gchar *format, ...)
4818{
4819 va_list args;
4820 gchar buf[BUFFSIZE + TIME_LEN];
4821 time_t t;
4822
4823 time(&t);
4824 strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t));
4825
4826 va_start(args, format);
4827 g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args);
4828 va_end(args);
4829
4830 g_warning("%s", buf);
4831 log_error_ui_func(buf + TIME_LEN);
4832 S_LOCK(log_fp);
4833 if (log_fp) {
4834 fwrite(buf, TIME_LEN, 1, log_fp);
4835 fputs("*** error: ", log_fp);
4836 fputs(buf + TIME_LEN, log_fp);
4837 fflush(log_fp);
4838 }
4839 S_UNLOCK(log_fp);
4840}
4841
4842void log_flush(void)
4843{
4844 S_LOCK(log_fp);
4845 if (log_fp)
4846 fflush(log_fp);
4847 S_UNLOCK(log_fp);
4848 log_flush_ui_func();
4849}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2017 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __UTILS_H__
21#define __UTILS_H__
22
23#ifdef HAVE_CONFIG_H
24# include "config.h"
25#endif
26
27#include <glib.h>
28#include <stdio.h>
29#include <string.h>
30#include <stdlib.h>
31#include <unistd.h>
32#include <sys/types.h>
33#include <dirent.h>
34#include <time.h>
35#if HAVE_ALLOCA_H
36# include <alloca.h>
37#endif
38
39/* Wrappers for C library function that take pathname arguments. */
40#if GLIB_CHECK_VERSION(2, 6, 0)
41# include <glib/gstdio.h>
42#else
43
44#define g_open open
45#define g_rename rename
46#define g_mkdir mkdir
47#define g_stat stat
48#define g_lstat lstat
49#define g_unlink unlink
50#define g_remove remove
51#define g_rmdir rmdir
52#define g_fopen fopen
53#define g_freopen freopen
54
55#endif /* GLIB_CHECK_VERSION */
56
57#if !GLIB_CHECK_VERSION(2, 7, 0)
58
59#ifdef G_OS_UNIX
60#define g_chdir chdir
61#define g_chmod chmod
62#else
63gint g_chdir (const gchar *path);
64gint g_chmod (const gchar *path,
65 gint mode);
66#endif /* G_OS_UNIX */
67
68#endif /* !GLIB_CHECK_VERSION */
69
70#ifdef G_OS_UNIX
71#define syl_link link
72#else
73gint syl_link (const gchar *src,
74 const gchar *dest);
75#endif
76
77/* The AC_CHECK_SIZEOF() in configure fails for some machines.
78 * we provide some fallback values here */
79#if !SIZEOF_UNSIGNED_SHORT
80 #undef SIZEOF_UNSIGNED_SHORT
81 #define SIZEOF_UNSIGNED_SHORT 2
82#endif
83#if !SIZEOF_UNSIGNED_INT
84 #undef SIZEOF_UNSIGNED_INT
85 #define SIZEOF_UNSIGNED_INT 4
86#endif
87#if !SIZEOF_UNSIGNED_LONG
88 #undef SIZEOF_UNSIGNED_LONG
89 #define SIZEOF_UNSIGNED_LONG 4
90#endif
91
92#ifndef HAVE_U32_TYPEDEF
93 #undef u32 /* maybe there is a macro with this name */
94 typedef guint32 u32;
95 #define HAVE_U32_TYPEDEF
96#endif
97
98#if defined(G_OS_WIN32) && !defined(_WIN64) && defined(HAVE_64BIT_TIME_T)
99 /* for backward binary compatibility. Use only in struct definition and
100 pointer arguments. */
101 typedef gint32 stime_t;
102#else
103 typedef time_t stime_t;
104#endif
105
106#if !GLIB_CHECK_VERSION(2, 26, 0)
107#if (defined (_MSC_VER) || defined (__MINGW32__)) && !defined(_WIN64)
108 typedef struct _stat32 GStatBuf;
109#else
110 typedef struct stat GStatBuf;
111#endif
112#endif
113
114#ifndef BIG_ENDIAN_HOST
115 #if (G_BYTE_ORDER == G_BIG_ENDIAN)
116 #define BIG_ENDIAN_HOST 1
117 #endif
118#endif
119
120#define CHDIR_RETURN_IF_FAIL(dir) \
121{ \
122 if (change_dir(dir) < 0) return; \
123}
124
125#define CHDIR_RETURN_VAL_IF_FAIL(dir, val) \
126{ \
127 if (change_dir(dir) < 0) return val; \
128}
129
130#define Xalloca(ptr, size, iffail) \
131{ \
132 if ((ptr = alloca(size)) == NULL) { \
133 g_warning("can't allocate memory\n"); \
134 iffail; \
135 } \
136}
137
138#define Xstrdup_a(ptr, str, iffail) \
139{ \
140 gchar *__tmp; \
141 \
142 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
143 g_warning("can't allocate memory\n"); \
144 iffail; \
145 } else \
146 strcpy(__tmp, str); \
147 \
148 ptr = __tmp; \
149}
150
151#define Xstrndup_a(ptr, str, len, iffail) \
152{ \
153 gchar *__tmp; \
154 \
155 if ((__tmp = alloca(len + 1)) == NULL) { \
156 g_warning("can't allocate memory\n"); \
157 iffail; \
158 } else { \
159 strncpy(__tmp, str, len); \
160 __tmp[len] = '\0'; \
161 } \
162 \
163 ptr = __tmp; \
164}
165
166#define Xstrcat_a(ptr, str1, str2, iffail) \
167{ \
168 gchar *__tmp; \
169 gint len1, len2; \
170 \
171 len1 = strlen(str1); \
172 len2 = strlen(str2); \
173 if ((__tmp = alloca(len1 + len2 + 1)) == NULL) { \
174 g_warning("can't allocate memory\n"); \
175 iffail; \
176 } else { \
177 memcpy(__tmp, str1, len1); \
178 memcpy(__tmp + len1, str2, len2 + 1); \
179 } \
180 \
181 ptr = __tmp; \
182}
183
184#define AUTORELEASE_STR(str, iffail) \
185{ \
186 gchar *__str; \
187 Xstrdup_a(__str, str, iffail); \
188 g_free(str); \
189 str = __str; \
190}
191
192#define FILE_OP_ERROR(file, func) \
193{ \
194 fprintf(stderr, "%s: ", file); \
195 fflush(stderr); \
196 perror(func); \
197}
198
199typedef void (*UIUpdateFunc) (void);
200typedef void (*EventLoopFunc) (void);
201typedef void (*ProgressFunc) (gint cur,
202 gint total);
203typedef gchar * (*QueryPasswordFunc) (const gchar *server,
204 const gchar *user);
205typedef void (*LogFunc) (const gchar *str);
206typedef void (*LogFlushFunc) (void);
207
208/* for macro expansion */
209#define Str(x) #x
210#define Xstr(x) Str(x)
211
212void list_free_strings (GList *list);
213void slist_free_strings (GSList *list);
214
215void hash_free_strings (GHashTable *table);
216void hash_free_value_mem (GHashTable *table);
217
218gint str_case_equal (gconstpointer v,
219 gconstpointer v2);
220guint str_case_hash (gconstpointer key);
221
222void ptr_array_free_strings (GPtrArray *array);
223
224typedef gboolean (*StrFindFunc) (const gchar *haystack,
225 const gchar *needle);
226
227gboolean str_find (const gchar *haystack,
228 const gchar *needle);
229gboolean str_case_find (const gchar *haystack,
230 const gchar *needle);
231gboolean str_find_equal (const gchar *haystack,
232 const gchar *needle);
233gboolean str_case_find_equal (const gchar *haystack,
234 const gchar *needle);
235
236/* number-string conversion */
237gint to_number (const gchar *nstr);
238guint to_unumber (const gchar *nstr);
239gchar *itos_buf (gchar *nstr,
240 gint n);
241gchar *itos (gint n);
242gchar *utos_buf (gchar *nstr,
243 guint n);
244gchar *to_human_readable_buf (gchar *buf,
245 size_t bufsize,
246 gint64 size);
247gchar *to_human_readable (gint64 size);
248
249/* alternative string functions */
250gint strcmp2 (const gchar *s1,
251 const gchar *s2);
252gint path_cmp (const gchar *s1,
253 const gchar *s2);
254gboolean is_path_parent (const gchar *parent,
255 const gchar *child);
256
257gchar *strretchomp (gchar *str);
258gchar *strtailchomp (gchar *str,
259 gchar tail_char);
260gchar *strcrchomp (gchar *str);
261
262gchar *strcasestr (const gchar *haystack,
263 const gchar *needle);
264gpointer my_memmem (gconstpointer haystack,
265 size_t haystacklen,
266 gconstpointer needle,
267 size_t needlelen);
268
269gchar *strncpy2 (gchar *dest,
270 const gchar *src,
271 size_t n);
272
273gboolean str_has_suffix_case (const gchar *str,
274 const gchar *suffix);
275
276gint str_find_format_times (const gchar *haystack,
277 gchar ch);
278
279gboolean is_next_nonascii (const gchar *s);
280gint get_next_word_len (const gchar *s);
281
282/* functions for string parsing */
283gint subject_compare (const gchar *s1,
284 const gchar *s2);
285gint subject_compare_for_sort (const gchar *s1,
286 const gchar *s2);
287void trim_subject_for_compare (gchar *str);
288void trim_subject_for_sort (gchar *str);
289void trim_subject (gchar *str);
290void eliminate_parenthesis (gchar *str,
291 gchar op,
292 gchar cl);
293void extract_parenthesis (gchar *str,
294 gchar op,
295 gchar cl);
296void extract_parenthesis_with_escape (gchar *str,
297 gchar op,
298 gchar cl);
299
300void extract_parenthesis_with_skip_quote (gchar *str,
301 gchar quote_chr,
302 gchar op,
303 gchar cl);
304
305void eliminate_quote (gchar *str,
306 gchar quote_chr);
307void extract_quote (gchar *str,
308 gchar quote_chr);
309void extract_quote_with_escape (gchar *str,
310 gchar quote_chr);
311void eliminate_address_comment (gchar *str);
312gchar *strchr_with_skip_quote (const gchar *str,
313 gint quote_chr,
314 gint c);
315gchar *strrchr_with_skip_quote (const gchar *str,
316 gint quote_chr,
317 gint c);
318void extract_address (gchar *str);
319void extract_list_id_str (gchar *str);
320
321gchar *extract_addresses (const gchar *str);
322
323gchar *normalize_address_field (const gchar *str);
324
325gboolean address_equal (const gchar *addr1,
326 const gchar *addr2);
327
328GSList *address_list_append_orig (GSList *addr_list,
329 const gchar *str);
330GSList *address_list_append (GSList *addr_list,
331 const gchar *str);
332GSList *references_list_prepend (GSList *msgid_list,
333 const gchar *str);
334GSList *references_list_append (GSList *msgid_list,
335 const gchar *str);
336GSList *newsgroup_list_append (GSList *group_list,
337 const gchar *str);
338
339GList *add_history (GList *list,
340 const gchar *str);
341
342/* modify string */
343void remove_return (gchar *str);
344void remove_space (gchar *str);
345void unfold_line (gchar *str);
346void subst_char (gchar *str,
347 gchar orig,
348 gchar subst);
349void subst_chars (gchar *str,
350 gchar *orig,
351 gchar subst);
352void subst_null (gchar *str,
353 gint len,
354 gchar subst);
355void subst_control (gchar *str,
356 gchar subst);
357void subst_for_filename (gchar *str);
358
359gchar *get_alt_filename (const gchar *filename,
360 gint count);
361
362gboolean is_header_line (const gchar *str);
363gboolean is_ascii_str (const gchar *str);
364
365gint get_quote_level (const gchar *str);
366gint check_line_length (const gchar *str,
367 gint max_chars,
368 gint *line);
369
370gchar *strstr_with_skip_quote (const gchar *haystack,
371 const gchar *needle);
372gchar *strcasestr_with_skip_quote (const gchar *haystack,
373 const gchar *needle);
374gchar *strchr_parenthesis_close (const gchar *str,
375 gchar op,
376 gchar cl);
377
378gchar **strsplit_parenthesis (const gchar *str,
379 gchar op,
380 gchar cl,
381 gint max_tokens);
382gchar **strsplit_with_quote (const gchar *str,
383 const gchar *delim,
384 gint max_tokens);
385gchar **strsplit_csv (const gchar *str,
386 gchar delim,
387 gint max_tokens);
388
389gchar *strconcat_csv (gchar delim,
390 const gchar *field1,
391 ...) G_GNUC_MALLOC G_GNUC_NULL_TERMINATED;
392
393gchar *get_abbrev_newsgroup_name (const gchar *group,
394 gint len);
395gchar *trim_string (const gchar *str,
396 gint len);
397gchar *trim_string_before (const gchar *str,
398 gint len);
399
400GList *uri_list_extract_filenames (const gchar *uri_list);
401gboolean is_uri_string (const gchar *str);
402gchar *get_uri_path (const gchar *uri);
403gint get_uri_len (const gchar *str);
404void decode_uri (gchar *decoded_uri,
405 const gchar *encoded_uri);
406void decode_xdigit_encoded_str (gchar *decoded,
407 const gchar *encoded);
408gchar *encode_uri (const gchar *filename);
409gchar *uriencode_for_filename (const gchar *filename);
410gchar *uriencode_for_mailto (const gchar *mailto);
411gint scan_mailto_url (const gchar *mailto,
412 gchar **to,
413 gchar **cc,
414 gchar **bcc,
415 gchar **subject,
416 gchar **inreplyto,
417 gchar **body);
418
419void set_startup_dir (void);
420void set_rc_dir (const gchar *dir);
421
422/* return static strings */
423const gchar *get_startup_dir (void);
424const gchar *get_home_dir (void);
425const gchar *get_document_dir (void);
426const gchar *get_rc_dir (void);
427const gchar *get_old_rc_dir (void);
428const gchar *get_mail_base_dir (void);
429const gchar *get_news_cache_dir (void);
430const gchar *get_imap_cache_dir (void);
431const gchar *get_mime_tmp_dir (void);
432const gchar *get_template_dir (void);
433const gchar *get_tmp_dir (void);
434gchar *get_tmp_file (void);
435const gchar *get_domain_name (void);
436
437/* file / directory handling */
438off_t get_file_size (const gchar *file);
439off_t get_file_size_as_crlf (const gchar *file);
440off_t get_left_file_size (FILE *fp);
441
442gint get_last_empty_line_size (FILE *fp,
443 off_t size);
444
445gboolean file_exist (const gchar *file,
446 gboolean allow_fifo);
447gboolean is_dir_exist (const gchar *dir);
448gboolean is_file_entry_exist (const gchar *file);
449gboolean dirent_is_regular_file (struct dirent *d);
450gboolean dirent_is_directory (struct dirent *d);
451
452#define is_file_exist(file) file_exist(file, FALSE)
453#define is_file_or_fifo_exist(file) file_exist(file, TRUE)
454
455gint change_dir (const gchar *dir);
456gint make_dir (const gchar *dir);
457gint make_dir_hier (const gchar *dir);
458gint remove_all_files (const gchar *dir);
459gint remove_numbered_files (const gchar *dir,
460 guint first,
461 guint last);
462gint remove_all_numbered_files (const gchar *dir);
463gint remove_expired_files (const gchar *dir,
464 guint hours);
465gint remove_dir_recursive (const gchar *dir);
466gint rename_force (const gchar *oldpath,
467 const gchar *newpath);
468gint copy_file (const gchar *src,
469 const gchar *dest,
470 gboolean keep_backup);
471gint copy_dir (const gchar *src,
472 const gchar *dest);
473gint move_file (const gchar *src,
474 const gchar *dest,
475 gboolean overwrite);
476
477gint append_file_part (FILE *fp,
478 off_t offset,
479 size_t length,
480 FILE *dest_fp);
481gint copy_file_part (FILE *fp,
482 off_t offset,
483 size_t length,
484 const gchar *dest);
485
486gint copy_file_stream (FILE *fp,
487 FILE *dest_fp);
488
489gchar *canonicalize_str (const gchar *str);
490gint canonicalize_file (const gchar *src,
491 const gchar *dest);
492gint canonicalize_file_replace (const gchar *file);
493FILE *canonicalize_file_stream (FILE *fp,
494 gint *length);
495gint uncanonicalize_file (const gchar *src,
496 const gchar *dest);
497gint uncanonicalize_file_replace(const gchar *file);
498
499gchar *normalize_newlines (const gchar *str);
500gchar *strchomp_all (const gchar *str);
501
502FILE *get_outgoing_rfc2822_file (FILE *fp);
503gchar *get_outgoing_rfc2822_str (FILE *fp);
504gchar *generate_mime_boundary (const gchar *prefix);
505
506gint change_file_mode_rw (FILE *fp,
507 const gchar *file);
508FILE *my_tmpfile (void);
509FILE *str_open_as_stream (const gchar *str);
510gint str_write_to_file (const gchar *str,
511 const gchar *file);
512gchar *file_read_to_str (const gchar *file);
513gchar *file_read_stream_to_str (FILE *fp);
514
515/* process execution */
516gint execute_async (gchar *const argv[]);
517gint execute_sync (gchar *const argv[]);
518gint execute_command_line (const gchar *cmdline,
519 gboolean async);
520gint execute_command_line_async_wait
521 (const gchar *cmdline);
522
523gint execute_open_file (const gchar *file,
524 const gchar *content_type);
525gint execute_print_file (const gchar *file);
526gchar *get_command_output (const gchar *cmdline);
527
528/* open URI with external browser */
529gint open_uri (const gchar *uri,
530 const gchar *cmdline);
531
532/* play sound */
533gint play_sound (const gchar *file,
534 gboolean async);
535
536/* time functions */
537stime_t remote_tzoffset_sec (const gchar *zone);
538stime_t tzoffset_sec (stime_t *now);
539gchar *tzoffset_buf (gchar *buf,
540 stime_t *now);
541gchar *tzoffset (stime_t *now);
542void get_rfc822_date (gchar *buf,
543 gint len);
544
545size_t my_strftime (gchar *s,
546 size_t max,
547 const gchar *format,
548 const struct tm *tm);
549
550/* UI hints */
551void set_ui_update_func (UIUpdateFunc func);
552void ui_update (void);
553
554void set_event_loop_func (EventLoopFunc func);
555void event_loop_iterate (void);
556
557void set_progress_func (ProgressFunc func);
558void progress_show (gint cur,
559 gint total);
560
561/* user input */
562void set_input_query_password_func (QueryPasswordFunc func);
563
564gchar *input_query_password (const gchar *server,
565 const gchar *user);
566
567/* logging */
568void set_log_file (const gchar *filename);
569void close_log_file (void);
570void set_log_verbosity (gboolean verbose);
571gboolean get_debug_mode (void);
572void set_debug_mode (gboolean enable);
573
574void set_log_ui_func (LogFunc print_func,
575 LogFunc message_func,
576 LogFunc warning_func,
577 LogFunc error_func);
578void set_log_ui_func_full (LogFunc print_func,
579 LogFunc message_func,
580 LogFunc warning_func,
581 LogFunc error_func,
582 LogFlushFunc flush_func);
583
584void set_log_show_status_func (LogFunc status_func);
585
586void debug_print (const gchar *format, ...) G_GNUC_PRINTF(1, 2);
587void status_print (const gchar *format, ...) G_GNUC_PRINTF(1, 2);
588
589void log_write (const gchar *str,
590 const gchar *prefix);
591
592void log_print (const gchar *format, ...) G_GNUC_PRINTF(1, 2);
593void log_message (const gchar *format, ...) G_GNUC_PRINTF(1, 2);
594void log_warning (const gchar *format, ...) G_GNUC_PRINTF(1, 2);
595void log_error (const gchar *format, ...) G_GNUC_PRINTF(1, 2);
596
597void log_flush (void);
598
599#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include <ctype.h>
21
22#define UUDECODE(c) (c=='`' ? 0 : c - ' ')
23#define N64(i) (i & ~63)
24
25const char uudigit[64] =
26{
27 '`', '!', '"', '#', '$', '%', '&', '\'',
28 '(', ')', '*', '+', ',', '-', '.', '/',
29 '0', '1', '2', '3', '4', '5', '6', '7',
30 '8', '9', ':', ';', '<', '=', '>', '?',
31 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
32 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
33 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
34 'X', 'Y', 'Z', '[', '\\', ']', '^', '_'
35};
36
37int touufrombits(unsigned char *out, const unsigned char *in, int inlen)
38{
39 int len;
40
41 if (inlen > 45) return -1;
42 len = (inlen * 4 + 2) / 3 + 1;
43 *out++ = uudigit[inlen];
44
45 for (; inlen >= 3; inlen -= 3) {
46 *out++ = uudigit[in[0] >> 2];
47 *out++ = uudigit[((in[0] << 4) & 0x30) | (in[1] >> 4)];
48 *out++ = uudigit[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
49 *out++ = uudigit[in[2] & 0x3f];
50 in += 3;
51 }
52
53 if (inlen > 0) {
54 *out++ = uudigit[(in[0] >> 2)];
55 if (inlen == 1) {
56 *out++ = uudigit[((in[0] << 4) & 0x30)];
57 } else {
58 *out++ = uudigit[(((in[0] << 4) & 0x30) | (in[1] >> 4))] ;
59 *out++ = uudigit[((in[1] << 2) & 0x3c)];
60 }
61 }
62 *out = '\0';
63
64 return len;
65}
66
67int fromuutobits(char *out, const char *in)
68{
69 int len, outlen, inlen;
70 register unsigned char digit1, digit2;
71
72 outlen = UUDECODE(in[0]);
73 in += 1;
74 if(outlen < 0 || outlen > 45)
75 return -2;
76 if(outlen == 0)
77 return 0;
78 inlen = (outlen * 4 + 2) / 3;
79 len = 0;
80
81 for( ; inlen>0; inlen-=4) {
82 digit1 = UUDECODE(in[0]);
83 if (N64(digit1)) return -1;
84 digit2 = UUDECODE(in[1]);
85 if (N64(digit2)) return -1;
86 out[len++] = (digit1 << 2) | (digit2 >> 4);
87 if (inlen > 2) {
88 digit1 = UUDECODE(in[2]);
89 if (N64(digit1)) return -1;
90 out[len++] = (digit2 << 4) | (digit1 >> 2);
91 if (inlen > 3) {
92 digit2 = UUDECODE(in[3]);
93 if (N64(digit2)) return -1;
94 out[len++] = (digit1 << 6) | digit2;
95 }
96 }
97 in += 4;
98 }
99
100 return len == outlen ? len : -3;
101}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20int touufrombits(unsigned char *, const unsigned char *, int);
21int fromuutobits(char *, const char *);
22
23#define X_UUENCODE_END_LINE '`'
24#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2010 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "defs.h"
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <dirent.h>
29#include <sys/stat.h>
30#include <time.h>
31#include <unistd.h>
32#include <string.h>
33#include <errno.h>
34
35#undef MEASURE_TIME
36
37#include "folder.h"
38#include "virtual.h"
39#include "mh.h"
40#include "procmsg.h"
41#include "procheader.h"
42#include "filter.h"
43#include "utils.h"
44
45typedef struct _VirtualSearchInfo VirtualSearchInfo;
46typedef struct _SearchCacheInfo SearchCacheInfo;
47
48struct _VirtualSearchInfo {
49 FilterRule *rule;
50 GSList *mlist;
51 GHashTable *search_cache_table;
52 FILE *fp;
53 gboolean requires_full_headers;
54 gboolean exclude_trash;
55};
56
57struct _SearchCacheInfo {
58 FolderItem *folder;
59 guint msgnum;
60 off_t size;
61 time_t mtime;
62 MsgFlags flags;
63};
64
65enum
66{
67 SCACHE_NOT_EXIST = 0,
68 SCACHE_MATCHED = 1,
69 SCACHE_NOT_MATCHED = 2
70};
71
72static void virtual_folder_init (Folder *folder,
73 const gchar *name,
74 const gchar *path);
75
76static GHashTable *virtual_read_search_cache
77 (FolderItem *item);
78static void virtual_write_search_cache (FILE *fp,
79 FolderItem *item,
80 MsgInfo *msginfo,
81 gint matched);
82
83static GSList *virtual_search_folder (VirtualSearchInfo *info,
84 FolderItem *item);
85static gboolean virtual_search_recursive_func
86 (GNode *node,
87 gpointer data);
88
89static Folder *virtual_folder_new (const gchar *name,
90 const gchar *path);
91static void virtual_folder_destroy (Folder *folder);
92
93static GSList *virtual_get_msg_list (Folder *folder,
94 FolderItem *item,
95 gboolean use_cache);
96static gchar *virtual_fetch_msg (Folder *folder,
97 FolderItem *item,
98 gint num);
99static MsgInfo *virtual_get_msginfo (Folder *folder,
100 FolderItem *item,
101 gint num);
102static gint virtual_close (Folder *folder,
103 FolderItem *item);
104
105static gint virtual_scan_folder (Folder *folder,
106 FolderItem *item);
107
108static gint virtual_rename_folder (Folder *folder,
109 FolderItem *item,
110 const gchar *name);
111static gint virtual_move_folder (Folder *folder,
112 FolderItem *item,
113 FolderItem *new_parent);
114static gint virtual_remove_folder (Folder *folder,
115 FolderItem *item);
116
117static FolderClass virtual_class =
118{
119 F_VIRTUAL,
120
121 virtual_folder_new,
122 virtual_folder_destroy,
123
124 NULL,
125 NULL,
126
127 virtual_get_msg_list,
128 NULL,
129 virtual_fetch_msg,
130 virtual_get_msginfo,
131 NULL,
132 NULL,
133 NULL,
134 NULL,
135 NULL,
136 NULL,
137 NULL,
138 NULL,
139 NULL,
140 NULL,
141 NULL,
142 NULL,
143 virtual_close,
144 virtual_scan_folder,
145
146 NULL,
147 virtual_rename_folder,
148 virtual_move_folder,
149 virtual_remove_folder,
150};
151
152
153FolderClass *virtual_get_class(void)
154{
155 return &virtual_class;
156}
157
158static Folder *virtual_folder_new(const gchar *name, const gchar *path)
159{
160 Folder *folder;
161
162 folder = (Folder *)g_new0(VirtualFolder, 1);
163 virtual_folder_init(folder, name, path);
164
165 return folder;
166}
167
168static void virtual_folder_destroy(Folder *folder)
169{
170 folder_local_folder_destroy(LOCAL_FOLDER(folder));
171}
172
173static void virtual_folder_init(Folder *folder, const gchar *name,
174 const gchar *path)
175{
176 folder->klass = virtual_get_class();
177 folder_local_folder_init(folder, name, path);
178}
179
180guint sinfo_hash(gconstpointer key)
181{
182 const SearchCacheInfo *sinfo = key;
183 guint h;
184
185 h = (guint)sinfo->folder;
186 h ^= sinfo->msgnum;
187 h ^= (guint)sinfo->size;
188 h ^= (guint)sinfo->mtime;
189 /* h ^= (guint)sinfo->flags.tmp_flags; */
190 h ^= (guint)sinfo->flags.perm_flags;
191
192 /* g_print("path: %s, n = %u, hash = %u\n",
193 sinfo->folder->path, sinfo->msgnum, h); */
194
195 return h;
196}
197
198gint sinfo_equal(gconstpointer v, gconstpointer v2)
199{
200 const SearchCacheInfo *s1 = v;
201 const SearchCacheInfo *s2 = v2;
202
203 return (s1->folder == s2->folder && s1->msgnum == s2->msgnum &&
204 s1->size == s2->size && s1->mtime == s2->mtime &&
205 /* s1->flags.tmp_flags == s2->flags.tmp_flags && */
206 s1->flags.perm_flags == s2->flags.perm_flags);
207}
208
209#define READ_CACHE_DATA_INT(n, fp) \
210{ \
211 guint32 idata; \
212 \
213 if (fread(&idata, sizeof(idata), 1, fp) != 1) { \
214 g_warning("Cache data is corrupted\n"); \
215 fclose(fp); \
216 return table; \
217 } else \
218 n = idata; \
219}
220
221static GHashTable *virtual_read_search_cache(FolderItem *item)
222{
223 GHashTable *table;
224 gchar *path, *file;
225 FILE *fp;
226 gchar *id;
227 gint count = 0;
228
229 g_return_val_if_fail(item != NULL, NULL);
230
231 path = folder_item_get_path(item);
232 file = g_strconcat(path, G_DIR_SEPARATOR_S, SEARCH_CACHE, NULL);
233 debug_print("reading search cache: %s\n", file);
234 fp = procmsg_open_data_file(file, SEARCH_CACHE_VERSION, DATA_READ,
235 NULL, 0);
236 g_free(file);
237 g_free(path);
238 if (!fp)
239 return NULL;
240
241 table = g_hash_table_new(sinfo_hash, sinfo_equal);
242
243 while (procmsg_read_cache_data_str(fp, &id) == 0) {
244 FolderItem *folder;
245 guint32 msgnum;
246 off_t size;
247 time_t mtime;
248 MsgFlags flags;
249 gint matched;
250 SearchCacheInfo *sinfo;
251
252 folder = folder_find_item_from_identifier(id);
253 g_free(id);
254
255 while (fread(&msgnum, sizeof(msgnum), 1, fp) == 1) {
256 if (msgnum == 0)
257 break;
258
259 READ_CACHE_DATA_INT(size, fp);
260 READ_CACHE_DATA_INT(mtime, fp);
261 READ_CACHE_DATA_INT(flags.tmp_flags, fp);
262 READ_CACHE_DATA_INT(flags.perm_flags, fp);
263 READ_CACHE_DATA_INT(matched, fp);
264
265 if (folder) {
266 sinfo = g_new(SearchCacheInfo, 1);
267 sinfo->folder = folder;
268 sinfo->msgnum = msgnum;
269 sinfo->size = size;
270 sinfo->mtime = mtime;
271 sinfo->flags = flags;
272 g_hash_table_insert(table, sinfo,
273 GINT_TO_POINTER(matched));
274 ++count;
275 }
276 }
277 }
278
279 debug_print("%d cache items read.\n", count);
280
281 fclose(fp);
282 return table;
283}
284
285static void virtual_write_search_cache(FILE *fp, FolderItem *item,
286 MsgInfo *msginfo, gint matched)
287{
288 if (!item && !msginfo) {
289 WRITE_CACHE_DATA_INT(0, fp);
290 return;
291 }
292
293 if (item) {
294 gchar *id;
295
296 id = folder_item_get_identifier(item);
297 if (id) {
298 WRITE_CACHE_DATA(id, fp);
299 g_free(id);
300 }
301 }
302
303 if (msginfo) {
304 WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
305 WRITE_CACHE_DATA_INT(msginfo->size, fp);
306 WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
307 WRITE_CACHE_DATA_INT
308 ((msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK), fp);
309 WRITE_CACHE_DATA_INT(msginfo->flags.perm_flags, fp);
310 WRITE_CACHE_DATA_INT(matched, fp);
311 }
312}
313
314static void search_cache_free_func(gpointer key, gpointer value, gpointer data)
315{
316 g_free(key);
317}
318
319static void virtual_search_cache_free(GHashTable *table)
320{
321 if (table) {
322 g_hash_table_foreach(table, search_cache_free_func, NULL);
323 g_hash_table_destroy(table);
324 }
325}
326
327static GSList *virtual_search_folder(VirtualSearchInfo *info, FolderItem *item)
328{
329 GSList *match_list = NULL;
330 GSList *mlist;
331 GSList *cur;
332 FilterInfo fltinfo;
333 gint count = 1, total, ncachehit = 0;
334 GTimeVal tv_prev, tv_cur;
335
336 g_return_val_if_fail(info != NULL, NULL);
337 g_return_val_if_fail(info->rule != NULL, NULL);
338 g_return_val_if_fail(item != NULL, NULL);
339 g_return_val_if_fail(item->path != NULL, NULL);
340
341 /* prevent circular reference */
342 if (item->stype == F_VIRTUAL)
343 return NULL;
344
345 g_get_current_time(&tv_prev);
346 status_print(_("Searching %s ..."), item->path);
347
348 mlist = folder_item_get_msg_list(item, TRUE);
349 total = g_slist_length(mlist);
350
351 memset(&fltinfo, 0, sizeof(FilterInfo));
352
353 debug_print("start query search: %s\n", item->path);
354
355 virtual_write_search_cache(info->fp, item, NULL, 0);
356
357 for (cur = mlist; cur != NULL; cur = cur->next) {
358 MsgInfo *msginfo = (MsgInfo *)cur->data;
359 GSList *hlist;
360
361 g_get_current_time(&tv_cur);
362 if (tv_cur.tv_sec > tv_prev.tv_sec ||
363 tv_cur.tv_usec - tv_prev.tv_usec >
364 PROGRESS_UPDATE_INTERVAL * 1000) {
365 status_print(_("Searching %s (%d / %d)..."),
366 item->path, count, total);
367 tv_prev = tv_cur;
368 }
369 ++count;
370
371 if (info->search_cache_table) {
372 gint matched;
373 SearchCacheInfo sinfo;
374
375 sinfo.folder = item;
376 sinfo.msgnum = msginfo->msgnum;
377 sinfo.size = msginfo->size;
378 sinfo.mtime = msginfo->mtime;
379 sinfo.flags = msginfo->flags;
380
381 matched = (gint)g_hash_table_lookup
382 (info->search_cache_table, &sinfo);
383 if (matched == SCACHE_MATCHED) {
384 match_list = g_slist_prepend
385 (match_list, msginfo);
386 cur->data = NULL;
387 virtual_write_search_cache(info->fp, NULL,
388 msginfo, matched);
389 ++ncachehit;
390 continue;
391 } else if (matched == SCACHE_NOT_MATCHED) {
392 virtual_write_search_cache(info->fp, NULL,
393 msginfo, matched);
394 ++ncachehit;
395 continue;
396 }
397 }
398
399 fltinfo.flags = msginfo->flags;
400 if (info->requires_full_headers) {
401 gchar *file;
402
403 file = procmsg_get_message_file(msginfo);
404 hlist = procheader_get_header_list_from_file(file);
405 g_free(file);
406 } else
407 hlist = procheader_get_header_list_from_msginfo
408 (msginfo);
409 if (!hlist)
410 continue;
411
412 if (filter_match_rule(info->rule, msginfo, hlist, &fltinfo)) {
413 match_list = g_slist_prepend(match_list, msginfo);
414 cur->data = NULL;
415 virtual_write_search_cache(info->fp, NULL, msginfo,
416 SCACHE_MATCHED);
417 } else {
418 virtual_write_search_cache(info->fp, NULL, msginfo,
419 SCACHE_NOT_MATCHED);
420 }
421
422 procheader_header_list_destroy(hlist);
423 }
424
425 debug_print("%d cache hits (%d total)\n", ncachehit, total);
426
427 virtual_write_search_cache(info->fp, NULL, NULL, 0);
428 procmsg_msg_list_free(mlist);
429
430 return g_slist_reverse(match_list);
431}
432
433static gboolean virtual_search_recursive_func(GNode *node, gpointer data)
434{
435 VirtualSearchInfo *info = (VirtualSearchInfo *)data;
436 FolderItem *item;
437 GSList *mlist;
438
439 g_return_val_if_fail(node->data != NULL, FALSE);
440
441 item = FOLDER_ITEM(node->data);
442
443 if (!item->path)
444 return FALSE;
445 if (info->exclude_trash && item->stype == F_TRASH)
446 return FALSE;
447
448 mlist = virtual_search_folder(info, item);
449 info->mlist = g_slist_concat(info->mlist, mlist);
450
451 return FALSE;
452}
453
454static GSList *virtual_get_msg_list(Folder *folder, FolderItem *item,
455 gboolean use_cache)
456{
457 GSList *mlist = NULL;
458 GSList *flist;
459 GSList *cur;
460 FilterRule *rule;
461 gchar *path;
462 gchar *rule_file;
463 gchar *cache_file;
464 FolderItem *target;
465 gint new = 0, unread = 0, total = 0;
466 VirtualSearchInfo info;
467
468 g_return_val_if_fail(item != NULL, NULL);
469 g_return_val_if_fail(item->stype == F_VIRTUAL, NULL);
470
471 path = folder_item_get_path(item);
472 rule_file = g_strconcat(path, G_DIR_SEPARATOR_S, "filter.xml", NULL);
473 flist = filter_read_file(rule_file);
474 g_free(rule_file);
475
476 g_free(path);
477
478 if (!flist) {
479 g_warning("filter rule not found\n");
480 return NULL;
481 }
482
483 rule = (FilterRule *)flist->data;
484 target = folder_find_item_from_identifier(rule->target_folder);
485
486 if (!target || target == item) {
487 g_warning("invalid target folder\n");
488 goto finish;
489 }
490
491 info.rule = rule;
492 info.mlist = NULL;
493 if (use_cache)
494 info.search_cache_table = virtual_read_search_cache(item);
495 else
496 info.search_cache_table = NULL;
497
498 path = folder_item_get_path(item);
499 cache_file = g_strconcat(path, G_DIR_SEPARATOR_S, SEARCH_CACHE, NULL);
500 info.fp = procmsg_open_data_file(cache_file, SEARCH_CACHE_VERSION,
501 DATA_WRITE, NULL, 0);
502 g_free(cache_file);
503 g_free(path);
504 if (!info.fp)
505 goto finish;
506
507 info.requires_full_headers =
508 filter_rule_requires_full_headers(rule);
509
510 if (rule->recursive) {
511 if (target->stype == F_TRASH)
512 info.exclude_trash = FALSE;
513 else
514 info.exclude_trash = TRUE;
515 } else
516 info.exclude_trash = FALSE;
517
518 if (rule->recursive) {
519 g_node_traverse(target->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
520 virtual_search_recursive_func, &info);
521 mlist = info.mlist;
522 } else
523 mlist = virtual_search_folder(&info, target);
524
525 fclose(info.fp);
526 virtual_search_cache_free(info.search_cache_table);
527
528 for (cur = mlist; cur != NULL; cur = cur->next) {
529 MsgInfo *msginfo = (MsgInfo *)cur->data;
530
531 if (MSG_IS_NEW(msginfo->flags))
532 ++new;
533 if (MSG_IS_UNREAD(msginfo->flags))
534 ++unread;
535 ++total;
536 }
537
538 item->new = new;
539 item->unread = unread;
540 item->total = total;
541 item->updated = TRUE;
542
543finish:
544 filter_rule_list_free(flist);
545 return mlist;
546}
547
548static gchar *virtual_fetch_msg(Folder *folder, FolderItem *item, gint num)
549{
550 return NULL;
551}
552
553static MsgInfo *virtual_get_msginfo(Folder *folder, FolderItem *item, gint num)
554{
555 return NULL;
556}
557
558static gint virtual_close(Folder *folder, FolderItem *item)
559{
560 return 0;
561}
562
563static gint virtual_scan_folder(Folder *folder, FolderItem *item)
564{
565 return 0;
566}
567
568static gint virtual_rename_folder(Folder *folder, FolderItem *item,
569 const gchar *name)
570{
571 g_return_val_if_fail(item != NULL, -1);
572 g_return_val_if_fail(item->stype == F_VIRTUAL, -1);
573
574 return mh_get_class()->rename_folder(folder, item, name);
575}
576
577static gint virtual_move_folder(Folder *folder, FolderItem *item,
578 FolderItem *new_parent)
579{
580 g_return_val_if_fail(item != NULL, -1);
581 g_return_val_if_fail(item->stype == F_VIRTUAL, -1);
582
583 return mh_get_class()->move_folder(folder, item, new_parent);
584}
585
586static gint virtual_remove_folder(Folder *folder, FolderItem *item)
587{
588 gchar *path;
589
590 g_return_val_if_fail(item != NULL, -1);
591 g_return_val_if_fail(item->stype == F_VIRTUAL, -1);
592
593 path = folder_item_get_path(item);
594 if (remove_dir_recursive(path) < 0) {
595 g_warning("can't remove directory '%s'\n", path);
596 g_free(path);
597 return -1;
598 }
599
600 g_free(path);
601 folder_item_remove(item);
602 return 0;
603}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __VIRTUAL_H__
21#define __VIRTUAL_H__
22
23#include <glib.h>
24
25#include "folder.h"
26
27typedef struct _VirtualFolder VirtualFolder;
28
29#define VIRTUAL_FOLDER(obj) ((VirtualFolder *)obj)
30
31struct _VirtualFolder
32{
33 LocalFolder lfolder;
34};
35
36FolderClass *virtual_get_class (void);
37
38#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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include <glib.h>
21#include <stdio.h>
22#include <string.h>
23#include <ctype.h>
24
25#include "xml.h"
26#include "utils.h"
27#include "codeconv.h"
28
29#define SPARSE_MEMORY
30/* if this is defined all attr.names and tag.names are stored
31 * in a hash table */
32#if defined(SPARSE_MEMORY)
33#include "stringtable.h"
34
35static StringTable *xml_string_table;
36
37static void xml_string_table_create(void)
38{
39 if (xml_string_table == NULL)
40 xml_string_table = string_table_new();
41}
42#define XML_STRING_ADD(str) \
43 string_table_insert_string(xml_string_table, (str))
44#define XML_STRING_FREE(str) \
45 string_table_free_string(xml_string_table, (str))
46
47#define XML_STRING_TABLE_CREATE() \
48 xml_string_table_create()
49
50#else /* !SPARSE_MEMORY */
51
52#define XML_STRING_ADD(str) \
53 g_strdup(str)
54#define XML_STRING_FREE(str) \
55 g_free(str)
56
57#define XML_STRING_TABLE_CREATE()
58
59#endif /* SPARSE_MEMORY */
60
61static void xml_free_tag (XMLTag *tag);
62static gint xml_get_parenthesis (XMLFile *file,
63 gchar *buf,
64 gint len);
65
66XMLFile *xml_open_file(const gchar *path)
67{
68 XMLFile *newfile;
69
70 g_return_val_if_fail(path != NULL, NULL);
71
72 XML_STRING_TABLE_CREATE();
73
74 newfile = g_new(XMLFile, 1);
75
76 newfile->fp = g_fopen(path, "rb");
77 if (!newfile->fp) {
78 g_free(newfile);
79 return NULL;
80 }
81
82 newfile->buf = g_string_new(NULL);
83 newfile->bufp = newfile->buf->str;
84
85 newfile->dtd = NULL;
86 newfile->encoding = NULL;
87 newfile->tag_stack = NULL;
88 newfile->level = 0;
89 newfile->is_empty_element = FALSE;
90
91 return newfile;
92}
93
94void xml_close_file(XMLFile *file)
95{
96 g_return_if_fail(file != NULL);
97
98 if (file->fp) fclose(file->fp);
99
100 g_string_free(file->buf, TRUE);
101
102 g_free(file->dtd);
103 g_free(file->encoding);
104
105 while (file->tag_stack != NULL)
106 xml_pop_tag(file);
107
108 g_free(file);
109}
110
111static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level)
112{
113 GNode *node = NULL;
114 XMLNode *xmlnode;
115 XMLTag *tag;
116
117 while (xml_parse_next_tag(file) == 0) {
118 if (file->level < level) break;
119 if (file->level == level) {
120 g_warning("xml_build_tree(): Parse error\n");
121 break;
122 }
123
124 tag = xml_get_current_tag(file);
125 if (!tag) break;
126 xmlnode = xml_node_new(xml_copy_tag(tag), NULL);
127 xmlnode->element = xml_get_element(file);
128 if (!parent)
129 node = g_node_new(xmlnode);
130 else
131 node = g_node_append_data(parent, xmlnode);
132
133 xml_build_tree(file, node, file->level);
134 if (file->level == 0) break;
135 }
136
137 return node;
138}
139
140GNode *xml_parse_file(const gchar *path)
141{
142 XMLFile *file;
143 GNode *node;
144
145 file = xml_open_file(path);
146 g_return_val_if_fail(file != NULL, NULL);
147
148 xml_get_dtd(file);
149
150 node = xml_build_tree(file, NULL, file->level);
151
152 xml_close_file(file);
153
154#if defined(SPARSE_MEMORY)
155 if (get_debug_mode())
156 string_table_get_stats(xml_string_table);
157#endif
158
159 return node;
160}
161
162gint xml_get_dtd(XMLFile *file)
163{
164 gchar buf[XMLBUFSIZE];
165 gchar *bufp = buf;
166
167 if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1;
168
169 if ((*bufp++ == '?') &&
170 (bufp = strcasestr(bufp, "xml")) &&
171 (bufp = strcasestr(bufp + 3, "version")) &&
172 (bufp = strchr(bufp + 7, '?'))) {
173 file->dtd = g_strdup(buf);
174 if ((bufp = strcasestr(buf, "encoding=\""))) {
175 bufp += 9;
176 extract_quote(bufp, '"');
177 file->encoding = g_strdup(bufp);
178 } else
179 file->encoding = g_strdup(CS_INTERNAL);
180 } else {
181 g_warning("Can't get xml dtd\n");
182 return -1;
183 }
184
185 return 0;
186}
187
188gint xml_parse_next_tag(XMLFile *file)
189{
190 gchar buf[XMLBUFSIZE];
191 gchar *bufp = buf;
192 gchar *tag_str;
193 XMLTag *tag;
194 gint len;
195
196 if (file->is_empty_element == TRUE) {
197 file->is_empty_element = FALSE;
198 xml_pop_tag(file);
199 return 0;
200 }
201
202 if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) {
203 g_warning("xml_parse_next_tag(): Can't parse next tag\n");
204 return -1;
205 }
206
207 /* end-tag */
208 if (buf[0] == '/') {
209 if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) {
210 g_warning("xml_parse_next_tag(): Tag name mismatch: %s\n", buf);
211 return -1;
212 }
213 xml_pop_tag(file);
214 return 0;
215 }
216
217 tag = xml_tag_new(NULL);
218 xml_push_tag(file, tag);
219
220 len = strlen(buf);
221 if (len > 0 && buf[len - 1] == '/') {
222 file->is_empty_element = TRUE;
223 buf[len - 1] = '\0';
224 g_strchomp(buf);
225 }
226 if (strlen(buf) == 0) {
227 g_warning("xml_parse_next_tag(): Tag name is empty\n");
228 return -1;
229 }
230
231 while (*bufp != '\0' && !g_ascii_isspace(*bufp)) bufp++;
232 if (*bufp == '\0') {
233 tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
234 if (tag_str) {
235 tag->tag = XML_STRING_ADD(tag_str);
236 g_free(tag_str);
237 } else
238 tag->tag = XML_STRING_ADD(buf);
239 return 0;
240 } else {
241 *bufp++ = '\0';
242 tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
243 if (tag_str) {
244 tag->tag = XML_STRING_ADD(tag_str);
245 g_free(tag_str);
246 } else
247 tag->tag = XML_STRING_ADD(buf);
248 }
249
250 /* parse attributes ( name=value ) */
251 while (*bufp) {
252 XMLAttr *attr;
253 gchar *attr_name;
254 gchar *attr_value;
255 gchar *utf8_attr_name;
256 gchar *utf8_attr_value;
257 gchar *p;
258 gchar quote;
259
260 while (g_ascii_isspace(*bufp)) bufp++;
261 attr_name = bufp;
262 if ((p = strchr(attr_name, '=')) == NULL) {
263 g_warning("xml_parse_next_tag(): Syntax error in tag\n");
264 return -1;
265 }
266 bufp = p;
267 *bufp++ = '\0';
268 while (g_ascii_isspace(*bufp)) bufp++;
269
270 if (*bufp != '"' && *bufp != '\'') {
271 g_warning("xml_parse_next_tag(): Syntax error in tag\n");
272 return -1;
273 }
274 quote = *bufp;
275 bufp++;
276 attr_value = bufp;
277 if ((p = strchr(attr_value, quote)) == NULL) {
278 g_warning("xml_parse_next_tag(): Syntax error in tag\n");
279 return -1;
280 }
281 bufp = p;
282 *bufp++ = '\0';
283
284 g_strchomp(attr_name);
285 xml_unescape_str(attr_value);
286 utf8_attr_name = conv_codeset_strdup
287 (attr_name, file->encoding, CS_INTERNAL);
288 utf8_attr_value = conv_codeset_strdup
289 (attr_value, file->encoding, CS_INTERNAL);
290 if (!utf8_attr_name)
291 utf8_attr_name = g_strdup(attr_name);
292 if (!utf8_attr_value)
293 utf8_attr_value = g_strdup(attr_value);
294
295 attr = xml_attr_new(utf8_attr_name, utf8_attr_value);
296 xml_tag_add_attr(tag, attr);
297
298 g_free(utf8_attr_value);
299 g_free(utf8_attr_name);
300 }
301
302 return 0;
303}
304
305void xml_push_tag(XMLFile *file, XMLTag *tag)
306{
307 g_return_if_fail(tag != NULL);
308
309 file->tag_stack = g_list_prepend(file->tag_stack, tag);
310 file->level++;
311}
312
313void xml_pop_tag(XMLFile *file)
314{
315 XMLTag *tag;
316
317 if (!file->tag_stack) return;
318
319 tag = (XMLTag *)file->tag_stack->data;
320
321 xml_free_tag(tag);
322 file->tag_stack = g_list_remove(file->tag_stack, tag);
323 file->level--;
324}
325
326XMLTag *xml_get_current_tag(XMLFile *file)
327{
328 if (file->tag_stack)
329 return (XMLTag *)file->tag_stack->data;
330 else
331 return NULL;
332}
333
334GList *xml_get_current_tag_attr(XMLFile *file)
335{
336 XMLTag *tag;
337
338 tag = xml_get_current_tag(file);
339 if (!tag) return NULL;
340
341 return tag->attr;
342}
343
344gchar *xml_get_element(XMLFile *file)
345{
346 gchar *str;
347 gchar *new_str;
348 gchar *end;
349
350 while ((end = strchr(file->bufp, '<')) == NULL)
351 if (xml_read_line(file) < 0) return NULL;
352
353 if (end == file->bufp)
354 return NULL;
355
356 str = g_strndup(file->bufp, end - file->bufp);
357 /* this is not XML1.0 strict */
358 g_strstrip(str);
359 xml_unescape_str(str);
360
361 file->bufp = end;
362 xml_truncate_buf(file);
363
364 if (str[0] == '\0') {
365 g_free(str);
366 return NULL;
367 }
368
369 new_str = conv_codeset_strdup(str, file->encoding, CS_INTERNAL);
370 if (!new_str)
371 new_str = g_strdup(str);
372 g_free(str);
373
374 return new_str;
375}
376
377gint xml_read_line(XMLFile *file)
378{
379 gchar buf[XMLBUFSIZE];
380 gint index;
381
382 if (fgets(buf, sizeof(buf), file->fp) == NULL)
383 return -1;
384
385 index = file->bufp - file->buf->str;
386
387 g_string_append(file->buf, buf);
388
389 file->bufp = file->buf->str + index;
390
391 return 0;
392}
393
394void xml_truncate_buf(XMLFile *file)
395{
396 gint len;
397
398 len = file->bufp - file->buf->str;
399 if (len > 0) {
400 g_string_erase(file->buf, 0, len);
401 file->bufp = file->buf->str;
402 }
403}
404
405gboolean xml_compare_tag(XMLFile *file, const gchar *name)
406{
407 XMLTag *tag;
408
409 tag = xml_get_current_tag(file);
410
411 if (tag && strcmp(tag->tag, name) == 0)
412 return TRUE;
413 else
414 return FALSE;
415}
416
417XMLNode *xml_node_new(XMLTag *tag, const gchar *text)
418{
419 XMLNode *node;
420
421 node = g_new(XMLNode, 1);
422 node->tag = tag;
423 node->element = g_strdup(text);
424
425 return node;
426}
427
428XMLTag *xml_tag_new(const gchar *tag)
429{
430 XMLTag *new_tag;
431
432 new_tag = g_new(XMLTag, 1);
433 if (tag)
434 new_tag->tag = XML_STRING_ADD(tag);
435 else
436 new_tag->tag = NULL;
437 new_tag->attr = NULL;
438
439 return new_tag;
440}
441
442XMLAttr *xml_attr_new(const gchar *name, const gchar *value)
443{
444 XMLAttr *new_attr;
445
446 new_attr = g_new(XMLAttr, 1);
447 new_attr->name = XML_STRING_ADD(name);
448 new_attr->value = g_strdup(value);
449
450 return new_attr;
451}
452
453void xml_tag_add_attr(XMLTag *tag, XMLAttr *attr)
454{
455 tag->attr = g_list_append(tag->attr, attr);
456}
457
458XMLTag *xml_copy_tag(XMLTag *tag)
459{
460 XMLTag *new_tag;
461 XMLAttr *attr;
462 GList *list;
463
464 new_tag = xml_tag_new(tag->tag);
465 for (list = tag->attr; list != NULL; list = list->next) {
466 attr = xml_copy_attr((XMLAttr *)list->data);
467 xml_tag_add_attr(new_tag, attr);
468 }
469
470 return new_tag;
471}
472
473XMLAttr *xml_copy_attr(XMLAttr *attr)
474{
475 return xml_attr_new(attr->name, attr->value);
476}
477
478gint xml_unescape_str(gchar *str)
479{
480 gchar *start;
481 gchar *end;
482 gchar *p = str;
483 gchar ch;
484 gint len;
485
486 while ((start = strchr(p, '&')) != NULL) {
487 if ((end = strchr(start + 1, ';')) == NULL) {
488 g_warning("Unescaped `&' appeared\n");
489 p = start + 1;
490 continue;
491 }
492 len = end - start + 1;
493 if (len < 3) {
494 p = end + 1;
495 continue;
496 }
497
498 if (!strncmp(start, "&lt;", 4))
499 ch = '<';
500 else if (!strncmp(start, "&gt;", 4))
501 ch = '>';
502 else if (!strncmp(start, "&amp;", 5))
503 ch = '&';
504 else if (!strncmp(start, "&apos;", 6))
505 ch = '\'';
506 else if (!strncmp(start, "&quot;", 6))
507 ch = '\"';
508 else {
509 p = end + 1;
510 continue;
511 }
512
513 *start = ch;
514 memmove(start + 1, end + 1, strlen(end + 1) + 1);
515 p = start + 1;
516 }
517
518 return 0;
519}
520
521gchar *xml_escape_str(const gchar *str)
522{
523 GString *estr;
524 const gchar *p;
525
526 if (!str)
527 return NULL;
528
529 estr = g_string_sized_new(strlen(str));
530 for (p = str; *p != '\0'; p++) {
531 switch (*p) {
532 case '<':
533 g_string_append(estr, "&lt;");
534 break;
535 case '>':
536 g_string_append(estr, "&gt;");
537 break;
538 case '&':
539 g_string_append(estr, "&amp;");
540 break;
541 case '\'':
542 g_string_append(estr, "&apos;");
543 break;
544 case '\"':
545 g_string_append(estr, "&quot;");
546 break;
547 default:
548 g_string_append_c(estr, *p);
549 break;
550 }
551 }
552
553 return g_string_free(estr, FALSE);
554}
555
556gint xml_file_put_escape_str(FILE *fp, const gchar *str)
557{
558 const gchar *p;
559
560 g_return_val_if_fail(fp != NULL, -1);
561
562 if (!str) return 0;
563
564 for (p = str; *p != '\0'; p++) {
565 switch (*p) {
566 case '<':
567 fputs("&lt;", fp);
568 break;
569 case '>':
570 fputs("&gt;", fp);
571 break;
572 case '&':
573 fputs("&amp;", fp);
574 break;
575 case '\'':
576 fputs("&apos;", fp);
577 break;
578 case '\"':
579 fputs("&quot;", fp);
580 break;
581 default:
582 fputc(*p, fp);
583 }
584 }
585
586 return 0;
587}
588
589gint xml_file_put_xml_decl(FILE *fp)
590{
591 g_return_val_if_fail(fp != NULL, -1);
592
593 fprintf(fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n", CS_INTERNAL);
594 return 0;
595}
596
597gint xml_file_put_node(FILE *fp, XMLNode *node)
598{
599 GList *cur;
600
601 g_return_val_if_fail(fp != NULL, -1);
602 g_return_val_if_fail(node != NULL, -1);
603
604 fprintf(fp, "<%s", node->tag->tag);
605
606 for (cur = node->tag->attr; cur != NULL; cur = cur->next) {
607 XMLAttr *attr = (XMLAttr *)cur->data;
608 fprintf(fp, " %s=\"", attr->name);
609 xml_file_put_escape_str(fp, attr->value);
610 fputs("\"", fp);
611 }
612
613 if (node->element) {
614 fputs(">", fp);
615 xml_file_put_escape_str(fp, node->element);
616 fprintf(fp, "</%s>\n", node->tag->tag);
617 } else {
618 fputs(" />\n", fp);
619 }
620
621 return 0;
622}
623
624void xml_free_node(XMLNode *node)
625{
626 if (!node) return;
627
628 xml_free_tag(node->tag);
629 g_free(node->element);
630 g_free(node);
631}
632
633static gboolean xml_free_func(GNode *node, gpointer data)
634{
635 XMLNode *xmlnode = node->data;
636
637 xml_free_node(xmlnode);
638 return FALSE;
639}
640
641void xml_free_tree(GNode *node)
642{
643 g_return_if_fail(node != NULL);
644
645 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func,
646 NULL);
647
648 g_node_destroy(node);
649}
650
651static void xml_free_tag(XMLTag *tag)
652{
653 if (!tag) return;
654
655 XML_STRING_FREE(tag->tag);
656 while (tag->attr != NULL) {
657 XMLAttr *attr = (XMLAttr *)tag->attr->data;
658 XML_STRING_FREE(attr->name);
659 g_free(attr->value);
660 g_free(attr);
661 tag->attr = g_list_remove(tag->attr, tag->attr->data);
662 }
663 g_free(tag);
664}
665
666static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len)
667{
668 gchar *start;
669 gchar *end;
670
671 buf[0] = '\0';
672
673 while ((start = strchr(file->bufp, '<')) == NULL)
674 if (xml_read_line(file) < 0) return -1;
675
676 start++;
677 file->bufp = start;
678
679 while ((end = strchr(file->bufp, '>')) == NULL)
680 if (xml_read_line(file) < 0) return -1;
681
682 strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len));
683 g_strstrip(buf);
684 file->bufp = end + 1;
685 xml_truncate_buf(file);
686
687 return 0;
688}
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 @@
1/*
2 * LibSylph -- E-Mail client library
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifndef __XML_H__
21#define __XML_H__
22
23#include <glib.h>
24#include <stdio.h>
25
26#define XMLBUFSIZE 8192
27
28typedef struct _XMLAttr XMLAttr;
29typedef struct _XMLTag XMLTag;
30typedef struct _XMLNode XMLNode;
31typedef struct _XMLFile XMLFile;
32
33struct _XMLAttr
34{
35 gchar *name;
36 gchar *value;
37};
38
39struct _XMLTag
40{
41 gchar *tag;
42 GList *attr;
43};
44
45struct _XMLNode
46{
47 XMLTag *tag;
48 gchar *element;
49};
50
51struct _XMLFile
52{
53 FILE *fp;
54
55 GString *buf;
56 gchar *bufp;
57
58 gchar *dtd;
59 gchar *encoding;
60
61 GList *tag_stack;
62 guint level;
63
64 gboolean is_empty_element;
65};
66
67XMLFile *xml_open_file (const gchar *path);
68void xml_close_file (XMLFile *file);
69GNode *xml_parse_file (const gchar *path);
70
71gint xml_get_dtd (XMLFile *file);
72gint xml_parse_next_tag (XMLFile *file);
73void xml_push_tag (XMLFile *file,
74 XMLTag *tag);
75void xml_pop_tag (XMLFile *file);
76
77XMLTag *xml_get_current_tag (XMLFile *file);
78GList *xml_get_current_tag_attr(XMLFile *file);
79gchar *xml_get_element (XMLFile *file);
80
81gint xml_read_line (XMLFile *file);
82void xml_truncate_buf (XMLFile *file);
83gboolean xml_compare_tag (XMLFile *file,
84 const gchar *name);
85
86XMLNode *xml_node_new (XMLTag *tag,
87 const gchar *text);
88XMLTag *xml_tag_new (const gchar *tag);
89XMLAttr *xml_attr_new (const gchar *name,
90 const gchar *value);
91void xml_tag_add_attr (XMLTag *tag,
92 XMLAttr *attr);
93
94XMLTag *xml_copy_tag (XMLTag *tag);
95XMLAttr *xml_copy_attr (XMLAttr *attr);
96
97gint xml_unescape_str (gchar *str);
98gchar *xml_escape_str (const gchar *str);
99gint xml_file_put_escape_str (FILE *fp,
100 const gchar *str);
101
102gint xml_file_put_xml_decl (FILE *fp);
103gint xml_file_put_node (FILE *fp,
104 XMLNode *node);
105
106void xml_free_node (XMLNode *node);
107void xml_free_tree (GNode *node);
108
109#endif /* __XML_H__ */